[comp.text] Balanced columns in troff

jjc@jclark.UUCP (James Clark) (02/22/90)

A while ago somebody (whose name I have forgotten - sorry) asked how
you could do balanced columns (ie multi-column pages where the columns
on the last page are balanced) in troff.  I think this is quite a
tricky problem and I don't remember seeing any solutions, so here is
my attempt at a solution.  I can't claim to have tested this very
thoroughly; but it seems to work.

This version provides keeps (bracketed by KS and KE) and variants
of .sp and .ne (.SP and .NE) that will work with balanced columns.

The strategy for balancing the columns on the last page is to test out
in a diversion increasingly longer column lengths, until a length is
found that works.  This strategy ensures that the balancing can be made
to respect keeps.

Unfortunately these macros would not be easy to merge with an existing
macro package.  I think balanced columns have to be designed in from
the start.

This is not intended to be a full-scale macro package: it's just a
demonstration of how balanced columns can be implemented in troff.

If you find any bugs, I would appreciate hearing about them.

James Clark
jjc@jclark.uucp
(if your mailer can't cope with that, jjc@ai.mit.edu should forward)

---cut here---
.\" tmac.bal - macros for balanced columns in troff.
.\" Written by James Clark <jjc@jclark.uucp>
.\" These macros are not copyrighted.
.nr NC 2   \" number of columns
.nr GW .5i \" gutter width
.nr CW \n(.lu-((\n(NCu-1u)*\n(GWu)/\n(NCu  \" column width
.nr HM 1i  \" head margin
.nr FM 1i  \" foot margin
.nr CL \n(.p-\n(FM-\n(HM  \" column length
.ll \n(CWu
.wh 0 HE
.wh -\n(FMu FO
.wh -.5i PF
.\" put environment 2 in no-fill mode
.ev 2
'nf
.ev
.nr LO 0 \" leftover flag: if non-zero, fool troff into not stopping yet
.\" handle top of page
.de HE
'sp |\\n(HMu
.di BD \" divert the body of the page
.\" accumulate at least as much as we need
.dt \\n(NCu*(\\n(CLu+1v+\\n(.Vu) DT
.ns \" no-space mode
..
.\" handle diversion trap
.de DT
.di
.nr CC 0 \" the current column
.\" we use environment 2 for rereading
.ev 2
.rn BD B2 \" avoid recursive diversions
.B2
.ev
..
.\" handle bottom of page
.de FO
.nr CC +1
.ie \\n(CC>=\\n(NC .NP
.el .SC
..
.\" start a new column
.de SC
'sp |\\n(HMu
.ns
'in +\\n(CWu+\\n(GWu
..
.\" start a new page
.de NP
'sp |(\\n(.pu-.5i)
'in 0
.\" if LO flag set, prevent troff stopping prematurely
.if \\n(LO \&\c
'bp
..
.\" print a footer
.\" don't do this in FO it doesn't seem to work on the last page
.de PF
.\" use environment 1 for printing the footer
.ev 1
.tl ''-%-''
.ev
..
.em EM
.de EM
.\" we don't want the break here to cause DT to be sprung, because
.\" in that case troff might prematurely quit
.rn DT D2
.de DT
\\..
.br
.rn D2 DT
.nr LO 0
.if '\\n(.z'BD' \{\
.br
.di
.ev 2
.\" GS is a guess of the appropriate column length
.nr GS \\n(dn/\\n(NC
.if \\n(GS>\\n(CL .nr GS \\n(CL
.if \\n(GS<1v .nr GS 1v
.TY
.\}
..
.\" like FO, but used while we are choosing the column length of the final page
.de TF
.nr CC +1
.ie \\n(CC>=\\n(NC \{\
.nr OV 1
.di
.\" divert any overflow into OF so that we can tell whether we actually
.\" overflowed or whether we just filled the page exactly.
.di OF
.\}
.el \{\
'sp |0
.\" troff doesn't spring it again unless we reset it
.dt \\n(GSu TF
.ns
.\}
..
.de TY
.nr OV 0 \" non-zero if we overflowed the page
.nr CC 0 \" current column
.\" see whether we would overflow the page if we used a column length of GS
.di TM
.dt \\n(GSu TF
.BD
.br
.di
.rm TM
.if \\n(OV \{\
.rm OF
.if !\\n(dn .nr OV 0 \" in this case, it just fitted
.\}
.ie !\\n(OV \{\
.\" it won't overflow so use GS as the column length
.ch FO \\n(.du+\\n(GSu
.nr CC 0
.BD
.\" make sure NP doesn't space back up the page
.nr CC \\n(NC
.\}
.el \{\
.\" GS is too small
.ie \\n(GS>=\\n(CL \{\
.\" we can't fit all of BD on this page
.\" so do this page normally
.nr CC 0
.nr LO 1
.\" avoid recursive diversions
.rn BD B2
.B2
.ev
.\" and try again with the next page
.EM
.\}
.el \{\
.\" try again with a larger value of GS
.nr GS +1v
.if \\n(GS>\\n(CL .nr GS \\n(CL
.TY
.\}
.\}
..
.\" delay calling a macro $1 with argument $2 until either there is
.\" no current diversion or the current diversion is TM
.de DY
.if '\\n(.z'' .\\$1 \\$2
.if '\\n(.z'TM' .\\$1 \\$2
.if !'\\n(.z'' .if !'\\n(.z'TM' \!.DY \\$1 \\$2
..
.\" start a keep
.de KS
.br
.di KP
..
.\" end a keep
.de KE
.br
.di
.NE \\n(dnu
.nr FI \\n(.f
.nf
.KP
.if \\n(FI .fi
..
.\" use this rather than .sp in normal text
.\" .sp won't always work because it may get truncated by the diversion trap
.de SP
.br
.ST \\$1
.sp \\$1
..
.\" If the current diversion is BD and there's less than $1v vertical space
.\" before the trap, then spring DT; otherwise if we are in a diversion,
.\" delay the execution of ST.
.de ST
.if !'\\n(.z'' \{\
.ie '\\n(.z'BD' \{\
.nr XX \\$1v
.if \\n(.t<\\n(XX .DT
.\}
.el \!.ST \\$1
.\}
..
.\" use this rather than ne in normal text
.de NE
.DY ne \\$1
..
.\" a simple paragraph macro
.de P
.br
.SP .4
.NE 1v+\n(.Vu \" keep the first two lines together
..
---cut here---