Commit Diff


commit - c5561c23cf394806cbf6d70a96f2dc0253f93745
commit + 5f1cf8e6fb130fd48d6f016d13baf5408b3181f8
blob - /dev/null
blob + a45a32ad7cabaff5e066e54688ae4480ff9e5708 (mode 644)
--- /dev/null
+++ src/cmd/mpm/.cvsignore
@@ -0,0 +1 @@
+pm
blob - /dev/null
blob + 5433ccc3236c8cde10d0fe4ce0683323b02310fa (mode 644)
--- /dev/null
+++ src/cmd/mpm/README
@@ -0,0 +1,188 @@
+An experiment in page makeup for troff output...
+
+-mpm is a version of standard -ms that causes extra
+information for vertical justification and figure
+placement to be included in troff output.  Commands that
+have been augmented to provide paddable space are
+
+	.SH and .NH
+	.PP and .LP	no space if \n(PD is 0; normally .nr PD 0.3v; leave at least 1u
+	.IP and .QP	also
+	.EQ and .EN
+	.TS and .TE	no space if \n(TS is 0; normally .nr TS 0.5v
+	.PS and .PE
+	.P1 and .P2	display programs in CW font
+	.DS and .DE
+	.QS and .QE
+
+Other commands, registers, strings, etc.:
+
+	.SP n		explicit paddable space, just like .sp n.
+			generally you should ALWAYS use .SP instead of .sp.
+			if you need exactly a given vertical space, you can say
+				.SP 3i exactly
+			this space won't be padded.
+	.Tm words	prints "words" and the output page number on stderr
+			sorry about the spelling; -ms pre-empted .TM
+	.NE n		like .ne.  note: does not cause a break
+
+			Others may be added as the need arises.
+
+	.nr FO n	Set the page length.  This value is the bottom of
+			the text on the page; a bottom title may lie below.
+			default is 10i (== 10 inches).
+	%o, %e		are strings containing odd and even page titles
+	%#		is the current page number (often useless)
+	.PT		is a macro invoked at the top of each "page";
+			it will normally use %e, %o and %#.  There is also
+			a .BT for page bottoms if desired.
+	.BP		force a page break
+	.FL		force all waiting figures out before any more running text
+	.1C, .2C	multiple columns;  number registers CW and GW set
+			the column and gutter width if you don't like the default.
+			absent a .FC command, all two-column contents collect
+			together on the page
+	.FC		freeze current two-column contents and start afresh.
+			necessary if you want to switch between 1 and 2 column
+			text and keep the relative order among them.
+
+Usage is some variant of
+
+	... | troff -mpm
+
+/usr/lib/tmac/pm is the page-justifier itself;  it is called automatically
+by the -mpm macro package.  If you are installing this yourself, you will
+have to edit the 2nd line of tmac.pm to arrange that pm is called directly
+from troff.
+
+There are several lines in tmac.pm that say
+	.so /n/coma/usr/bwk/...
+You should delete these;  they are placeholders for some experiments.
+
+If you use -mm, you are more or less out of luck, although we will be
+happy to provide a crude and incomplete program that purports to convert
+-mm to -ms.  It may suggest what you need but it won't do the job.
+
+To compile pm, you need a C++ compiler, preferably release 2.0 or later.
+Put the .c and .h files in a directory, and type
+	make
+This process may well fail.  The usual cause is that different systems
+put function declarations in different header files, and C++ insists that
+all functions be properly declared.  You can almost always get through this
+part by adding function declarations.  The most likely offender is malloc;
+a line like
+	extern char *malloc(int);
+near the top of slug.c will solve this one.
+
+
+Bugs, etc.:
+
+	not all -ms commands have been decorated;  in particular,
+	the rich variety of document types (TM, CSTR, etc.,) is not
+	really supported.
+
+	there are problems with funny first pages and troff input
+	that moves back up the page.
+
+	multiple columns:  only .2C is available.  The program does not check
+	whether something is wide or narrow:  user has responsibility to mark
+	which with .1C or .2C.
+
+	headings are a bit tricky if you want things like
+	running titles that include the current section title.
+	normally a two-pass procedure using .Tm is needed.
+	
+	It's a pain to force a blank vertical space of specified height.
+	Try this:
+		.de x
+		\v'\\$1'\0\h'-\w'\0'u'\c
+		..
+		.x 2.5i
+
+
+If you want to roll your own, the following components are
+included in pm's "command language".  They are inserted in
+the troff output in the form of "x X ..." commands, which
+are created either by \X'...' or by the .X macro in -mpm.
+Look at how they are used in /usr/lib/tmac/tmac.pm for examples.
+
+
+BS n	breakable stream	n = min # lines that must appear on page
+				use:  PP, LP, IP, ...
+
+US	unbreakable stream	use:  KS/KE, DS/DE, TS/TE, EQ/EN, PS/PE, etc.
+
+BF v	breakable float		v = preferred vertical location of box center
+				use:  FS/FE
+				use two successive BF's to give two preferences
+
+UF v	unbreakable float	v = preferred vertical location of box center
+				use:  KF/KE
+				use two successive UF's to give two preferences
+
+PT	page title		use:  user has absolute control between PT and END
+				no SP's or other pm commands inside are processed
+
+BT	bottom title		use:  user has absolute control between BT and END
+
+END	end			end a US, BF, UF, PT, or BT
+				all constructs nest, but a float within another float
+				or a US block will not float within or outside the block
+
+NE n	need			break page if a VBOX of height n would not fit on page
+				use:  .NE n
+
+SP n	space			paddable space of n
+				use:  .SP n
+
+PARM NP v			top of pm text at v
+	new page
+
+PARM FO v			bottom of pm text at v
+	footer			length of text on page = FO-NP
+
+PARM PL v			physical page ends at v
+	page length		default = FO + NP
+
+PARM MF x			tolerance to prevent padding
+	minimum fullness	default = 0.9
+
+PARM CT x			tolerance for two-column operation
+	column tolerance	default = 0.5
+
+PARM DBG x			debugging flag
+
+TM str	message			.Tm words prints <pageno> <tab> <words> on stderr
+
+MC n o	multiple column		n columns, offset o.
+				Only 1 and 2 columns will work.
+
+CMD BP	break page		force page break
+
+CMD FL	flush			force all queued figures out before any more
+				stream material is output
+
+CMD FC	freeze columns		force out current two-column contents;
+				start a fresh one
+
+Something like this will probably have to be added:
+
+NC	new column		HARD!
+
+Known botches in the existing implementation of pm:
+
+If a footnote is split across two pages, any associated separator line
+will not be copied.  If there are multiple footnotes on one page, there
+will be multiple separators too.  -mpm's .FS macro does not provide a
+separator.  If you want a separator line, put it in explicitly with
+a call to the .FA macro.
+
+There are not enough settable parameters;  in particular, the
+way to control the height is a botch.
+
+
+Historical note:  There is a simpler version of pm and -mpm
+called pj and -mpj that only does vertical justification of
+pages that have already been laid out by conventional means.
+This simpler version may be adequate, but it is no longer
+supported and memory of how it works is growing dim.
blob - /dev/null
blob + e0f303ad70346496165358a74525fe7fc948bf29 (mode 644)
--- /dev/null
+++ src/cmd/mpm/misc.cc
@@ -0,0 +1,12 @@
+#include	"misc.h"
+
+char	errbuf[200];
+char	*progname;
+int	wantwarn = 0;
+
+int	dbg	= 0;
+// dbg = 1 : dump slugs
+// dbg = 2 : dump ranges
+// dbg = 4 : report function entry
+// dbg = 8 : follow queue progress
+// dbg = 16: follow page fill progress
blob - /dev/null
blob + 682964b0ccfd83b51ed3f979229a6c6b958eef3e (mode 644)
--- /dev/null
+++ src/cmd/mpm/misc.h
@@ -0,0 +1,41 @@
+#include	<stdio.h>
+#include	<stdlib.h>
+#include	<math.h>
+#include	<ctype.h>
+#include	<string.h>
+
+// XXX: Apparently necessary for g++
+#define	typename tyname
+
+extern char	errbuf[];
+extern char	*progname;
+extern int	linenum;
+extern int	wantwarn;
+
+// #define	ERROR	fflush(stdout), fprintf(stderr, "%s: ", progname), fprintf(stderr,
+// #define	FATAL	), exit(1)
+// #define	WARNING	)
+
+#define	ERROR	fprintf(stdout, "\n#MESSAGE TO USER:  "), sprintf(errbuf,
+#define	FATAL	), fputs(errbuf, stdout), \
+		fprintf(stderr, "%s: ", progname), \
+		fputs(errbuf, stderr), \
+		fflush(stdout), \
+		exit(1)
+#define	WARNING	), fputs(errbuf, stdout), \
+		wantwarn ? \
+			fprintf(stderr, "%s: ", progname), \
+			fputs(errbuf, stderr) : 0, \
+		fflush(stdout)
+
+#define	eq(s,t)	(strcmp(s,t) == 0)
+
+inline int	max(int x, int y)	{ return x > y ? x : y; }
+inline int	min(int x, int y)	{ return x > y ? y : x; }
+inline int	abs(int x)		{ return (x >= 0) ? x : -x; }
+
+extern int	dbg;
+
+extern int	pn, userpn;		// actual and user-defined page numbers
+extern int	pagetop, pagebot;	// printing margins
+extern int	physbot;		// physical bottom of the page
blob - /dev/null
blob + 635edafaf2fbc92eae2718d326f47b05b2f57988 (mode 644)
--- /dev/null
+++ src/cmd/mpm/mkfile
@@ -0,0 +1,24 @@
+</$objtype/mkfile
+
+TARG=aux/pm
+OFILES=misc.$O\
+	slug.$O\
+	range.$O\
+	queue.$O\
+	page.$O\
+
+HFILES=misc.h\
+
+BIN=/$objtype/bin
+</sys/src/cmd/mkone
+CC=c++/$CC
+LD=c++/$LD
+CFLAGS=
+
+slug.$O:	slug.h
+range.$O:	range.h slug.h
+queue.$O:	page.h range.h slug.h
+page.$O:	page.h range.h slug.h
+
+test:V:	$O.out
+	tryout $O.out
blob - /dev/null
blob + 966edae9de14ba2ba6f9c27e0e50fe63e511343b (mode 644)
--- /dev/null
+++ src/cmd/mpm/page.cc
@@ -0,0 +1,612 @@
+#include	"misc.h"
+#include	"slug.h"
+#include	"range.h"
+#include	"page.h"
+
+const int	MAXRANGES	= 1000;
+static range *ptemp[MAXRANGES];		// for movefloats()
+
+static void swapright(int n)		// used by movefloats()
+{
+	range *t = ptemp[n];
+	ptemp[n] = ptemp[n+1];
+	ptemp[n+1] = t;
+	ptemp[n]->setaccum( ptemp[n+1]->accum() -
+			    ptemp[n+1]->rawht() + ptemp[n]->rawht() );
+	ptemp[n+1]->setaccum( ptemp[n]->accum() + ptemp[n+1]->rawht() );
+}
+
+// Figure out the goal position for each floating range on scratch,
+// and move it past stream ranges until it's as close to its goal as possible.
+static void movefloats(stream *scratch, double scale)
+{
+	int nranges, i;
+	const int Huge = 100000;
+
+	for (nranges = 0; scratch->more(); scratch->advance())
+		ptemp[nranges++] = scratch->current();
+	scratch->freeall();
+	ufrange rtemp;
+	ptemp[nranges] = &rtemp;
+	rtemp.setgoal(Huge);
+	int accumV = 0;				// compute accum values and
+	for (i = 0; i < nranges; i++) {		// pick closest goal for floats
+		ptemp[i]->pickgoal(accumV, scale);
+		ptemp[i]->setaccum(accumV += ptemp[i]->rawht());
+	}
+	int j;					// index for inner loop below:
+	for (i = nranges; --i >= 0; )		// stably sort floats to bottom
+		for (j = i; j < nranges; j++)
+			if (ptemp[j]->goal() > ptemp[j+1]->goal())
+				swapright(j);
+			else
+				break;
+	if (dbg & 16)
+		printf("#movefloats:  before floating, from bottom:\n");
+	for (i = nranges; --i >= 0; ) {		// find topmost float
+		if (ptemp[i]->goal() == NOGOAL)
+			break;
+		if (dbg & 16)
+			printf("# serialno %d goal %d height %d\n",
+				ptemp[i]->serialno(), ptemp[i]->goal(),
+				ptemp[i]->rawht());
+	}					// i+1 is topmost float
+	for (i++ ; i < nranges; i++)		// move each float up the page
+		for (j = i; j > 0; j--)		// as long as closer to its goal
+			if (ptemp[j]->goal()
+			  <= ptemp[j-1]->accum() + ptemp[j]->rawht()/2
+			  && ptemp[j-1]->goal() == NOGOAL)
+				swapright(j-1);
+			else
+				break;
+	if (ptemp[nranges] != &rtemp)
+		ERROR "goal sentinel has disappeared from movefloats" FATAL;
+	for (i = 0; i < nranges; i++)		// copy sorted list back
+		scratch->append(ptemp[i]);
+}
+
+// Traverse the leaves of a tree of ranges, filtering out only SP and VBOX.
+static range *filter(generator *g)
+{
+	range *r;
+	while ((r = g->next()) != 0)
+		if (r->isvbox() || r->issp())
+			break;
+	return r;
+}
+
+// Zero out leading and trailing spaces; coalesce adjacent SP's.
+static void trimspace(stream *scratch)
+{
+	generator g;
+	range *r, *prevr = 0;
+
+	for (g = scratch; (r = filter(&g)) != 0 && r->issp(); prevr = r)
+		r->setheight(0);		// zap leading SP
+	for ( ; (r = filter(&g)) != 0; prevr = r)
+		if (r->issp())
+			if (prevr && prevr->issp()) {
+						// coalesce adjacent SPs
+				r->setheight(max(r->rawht(), prevr->height()));
+				prevr->setheight(0);
+			} else			// a VBOX intervened
+				r->setheight(r->rawht());
+	if (prevr && prevr->issp())		// zap *all* trailing space
+		prevr->setheight(0);		// (since it all coalesced
+						// into the last one)
+}
+
+// Pad the non-zero SP's in scratch so the total height is wantht.
+// Note that the SP values in scratch are not the raw values, and
+// indeed may already have been padded.
+static void justify(stream *scratch, int wantht)
+{
+	range *r;
+	int nsp = 0, hsp = 0;
+
+	int adjht = scratch->height();
+					// Find all the spaces.
+	generator g;
+	for (g = scratch; (r = g.next()) != 0; )
+		if (r->issp() && r->height() > 0) {
+			nsp++;
+			hsp += r->height();
+		}
+	int excess = wantht - adjht;
+	if (excess < 0)
+		ERROR "something on page %d is oversize by %d\n",
+			userpn, -excess WARNING;
+	if (dbg & 16)
+		printf("# justify %d: excess %d nsp %d hsp %d adjht %d\n",
+			userpn, excess, nsp, hsp, adjht);
+	if (excess <= 0 || nsp == 0)
+		return;
+					// Redistribute the excess space.
+	for (g = scratch; (r = g.next()) != 0; )
+		if (r->issp() && r->height() > 0) {
+			int delta = (int) ((float)(r->height()*excess)/hsp + 0.5);
+			if (dbg & 16)
+				printf("# pad space %d by %d: hsp %d excess %d\n",
+					r->height(), delta, hsp, excess);
+			r->setheight(r->height() + delta);
+		}
+}
+
+// If r were added to s, would the height of the composed result be at most maxht?
+int wouldfit(range *r, stream *s, int maxht)
+{
+	if (r->rawht() + s->rawht() <= maxht)
+		return 1;		// the conservative test succeeded
+	stream scratch;			// local playground for costly test
+	for (stream cd = *s; cd.more(); cd.advance())
+		scratch.append(cd.current());
+	scratch.append(r);
+	movefloats(&scratch, ((double) scratch.rawht())/maxht);
+	trimspace(&scratch);
+	int retval = scratch.height() <= maxht;
+	scratch.freeall();
+	return retval;
+}
+
+// If s1 were added to s, would the height of the composed result be at most maxht?
+// The computational structure is similar to that above.
+int wouldfit(stream *s1, stream *s, int maxht)
+{
+	if (s1->rawht() + s->rawht() <= maxht)
+		return 1;
+	stream scratch, cd;
+	for (cd = *s; cd.more(); cd.advance())
+		scratch.append(cd.current());
+	for (cd = *s1; cd.more(); cd.advance())
+		scratch.append(cd.current());
+	movefloats(&scratch, ((double) scratch.rawht())/maxht);
+	trimspace(&scratch);
+	int retval = scratch.height() <= maxht;
+	scratch.freeall();
+	return retval;
+}
+
+// All of stream *s is destined for one column or the other; which is it to be?
+void multicol::choosecol(stream *s, int goalht)
+{
+	stream *dest;
+	if (!leftblocked && wouldfit(s, &(column[0]), goalht))
+		dest = &(column[0]);
+	else {
+		dest = &(column[1]);
+		if (!s->current()->floatable())
+					// a stream item is going into the right
+					// column, so no more can go into the left.
+			leftblocked = 1;
+	}
+	for (stream cd = *s; cd.more(); cd.advance())
+		dest->append(cd.current());
+}
+
+double coltol = 0.5;
+
+// Try, very hard, to put everything in the multicol into two columns
+// so that the total height is at most htavail.
+void multicol::compose(int defonly)
+{
+	if (!nonempty()) {
+		setheight(0);
+		return;
+	}
+	scratch.freeall();	// fill scratch with everything destined
+				// for either column
+	stream cd;
+	for (cd = definite; cd.more(); cd.advance())
+		scratch.append(cd.current());
+	if (!defonly)
+		for (cd = *(currpage->stage); cd.more(); cd.advance())
+			if (cd.current()->numcol() == 2)
+				scratch.append(cd.current());
+	scratch.restoreall();		// in particular, floatables' goals
+	int i;
+	int rawht = scratch.rawht();
+	int halfheight = (int)(coltol*rawht);
+					// choose a goal height
+	int maxht = defonly ? halfheight : htavail;
+secondtry:
+	for (i = 0; i < 2; i++)
+		column[i].freeall();
+	leftblocked = 0;
+	cd = scratch;
+	while (cd.more()) {
+		queue ministage;	// for the minimally acceptable chunks
+		ministage.freeall();	// that are to be added to either column
+		while (cd.more() && !cd.current()->issentinel()) {
+			ministage.enqueue(cd.current());
+			cd.advance();
+		}
+		choosecol(&ministage, maxht);
+		if (cd.more() && cd.current()->issentinel())
+			cd.advance();	// past sentinel
+	}
+	if (height() > htavail && maxht != htavail) {
+					// We tried to balance the columns, but
+					// the result was too tall.  Go back
+					// and try again with the less ambitious
+					// goal of fitting the space available.
+		maxht = htavail;
+		goto secondtry;
+	}
+	for (i = 0; i < 2; i++) {
+		movefloats(&(column[i]), ((double) column[i].rawht())/currpage->pagesize);
+		trimspace(&(column[i]));
+	}
+	if (dbg & 32) {
+		printf("#multicol::compose: htavail %d maxht %d dv %d\n",
+			htavail, maxht, height());
+		dump();
+	}
+	if (defonly)
+		stretch(height());
+}
+
+// A sequence of two-column ranges waits on the stage.
+// So long as the page's skeleton hasn't changed--that is, the maximum height
+// available to the two-column chunk is the same--we just use the columns that
+// have been built up so far, and choose a column into which to put the stage.
+// If the skeleton has changed, however, then we may need to make entirely
+// new decisions about which column gets what, so we recompose the whole page.
+void multicol::tryout()
+{
+	if (htavail == prevhtavail)
+		choosecol(currpage->stage, htavail);
+	else
+		currpage->compose(DRAFT);
+	prevhtavail = htavail;
+}
+
+// Make both columns the same height.
+// (Maybe this should also be governed by minfull,
+// to prevent padding very underfull columns.)
+void multicol::stretch(int wantht)
+{
+	if (wantht < height())
+		ERROR "page %d: two-column chunk cannot shrink\n", userpn FATAL;
+	for (int i = 0; i < 2; i++)
+		justify(&(column[i]), wantht);
+	if (dbg & 16)
+		printf("#col hts: left %d right %d\n",
+			column[0].height(), column[1].height());
+}
+
+// Report an upper bound on how tall the current two-column object is.
+// The (possibly composed) heights of the two columns give a crude upper
+// bound on the total height.  If the result is more than the height
+// available for the two-column object, then the columns are each
+// composed to give a better estimate of their heights.
+int multicol::height()
+{
+	int retval = max(column[0].height(), column[1].height());
+	if (retval < htavail)
+		return retval;
+	for (int i = 0; i < 2; i++) {
+		movefloats(&(column[i]), ((double) column[i].height())/currpage->pagesize);
+		trimspace(&(column[i]));
+	}
+	return max(column[0].height(), column[1].height());
+}
+
+void multicol::dump()
+{
+	printf("####2COL dv %d\n", height());
+	printf("# left column:\n");
+	column[0].dump();
+	printf("# right column:\n");
+	column[1].dump();
+}
+
+// From the head of queue qp, peel off a piece whose raw height is at most space.
+int peeloff(stream *qp, int space)
+{
+	stream *s1 = qp->current()->children();
+	if (!(s1 && s1->more() && s1->current()->height() <= space))
+					// in other words, either qp's head is
+					// not nested, or its first subrange
+		return 0;		// is also too big, so we give up
+	qp->split();
+	s1 = qp->current()->children();
+	stream *s2 = qp->next()->children();
+	while (s2->more() && s2->current()->rawht() <= space) {
+		s1->append(s2->current());
+		space -= s2->current()->rawht();
+		s2->advance();
+	}
+	return 1;
+}
+
+// There are four possibilities for consecutive calls to tryout().
+// If we're processing a sequence of single-column ranges, tryout()
+// uses the original algorithm: (1) conservative test; (2) costly test;
+// (3) split a breakable item.
+// If we're processing a sequence of double-column ranges, tryout()
+// defers to twocol->tryout(), which gradually builds up the contents
+// of the two columns until they're as tall as they can be without
+// exceeding twocol->htavail.
+// If we're processing a sequence of single-column ranges and we
+// get a double-column range, then we use compose() to build a
+// skeleton page and set twocol->htavail, the maximum height that
+// should be occupied by twocol.
+// If we're processing a sequence of double-column ranges and we
+// get a single-column range, then we should go back and squish
+// the double-column chunk as short as possible before we see if
+// we can fit the single-column range.
+void page::tryout()
+{
+	if (!stage->more())
+		ERROR "empty stage in page::tryout()\n" FATAL;
+	int curnumcol = stage->current()->numcol();
+	if (dbg & 32) {
+		printf("#page::tryout(): ncol = %d, prevncol = %d; on stage:\n",
+			curnumcol, prevncol);
+		stage->dump();
+		printf("#END of stage contents\n");
+	}
+	switch(curnumcol) {
+	default:
+		ERROR "unexpected number of columns in tryout(): %d\n",
+			stage->current()->numcol() FATAL;
+		break;
+	case 1:
+		if (prevncol == 2)
+			compose(FINAL);
+		if (wouldfit(stage, &definite, pagesize - twocol->height()))
+			commit();
+		else if (stage->current()->breakable() || blank()
+			&& peeloff(stage,
+				pagesize - (definite.height() + twocol->height()))) {
+			// first add the peeled-off part that fits
+			adddef(stage->dequeue());
+			// then send the rest back for later
+			stage->current()->setbreaking();
+			welsh();
+		} else if (blank()) {
+			stage->current()->rdump();
+			ERROR "A %s is too big to continue.\n",
+			stage->current()->typename() FATAL;
+		} else
+			welsh();
+		break;
+	case 2:
+		if (prevncol == 1)
+			compose(DRAFT);
+		else
+			twocol->tryout();
+		if (scratch.height() <= pagesize)
+			commit();
+		else
+			welsh();
+		break;
+	}
+	prevncol = curnumcol;
+}
+
+// To compose the page, we (1) fill scratch with the stuff that's meant to
+// go on the page; (2) compose scratch as best we can; (3) set the maximum
+// height available to the two-column part of the page; (4) have the two-
+// column part compose itself.
+// In the computation of twocol->htavail, it does not matter that
+// twocol->height() is merely an upper bound, because it is merely being
+// subtracted out to give the exact total height of the single-column stuff.
+void page::compose(int final)
+{
+	makescratch(final);
+	int adjht = scratch.rawht();
+	if (dbg & 16)
+		printf("# page %d measure %d\n", userpn, adjht);
+	movefloats(&scratch, ((double) adjht)/pagesize);
+	trimspace(&scratch);
+	twocol->htavail = pagesize - (scratch.height() - twocol->height());
+	twocol->compose(final);
+	adjht = scratch.height();
+	if (dbg & 16)
+		printf("# page %d measure %d after trim\n", userpn, adjht);
+}
+
+// Fill the scratch area with ranges destined for the page.
+// If defonly == 0, then add anything that's on stage--this is a trial run.
+// If defonly != 0, use only what's definitely on the page.
+void page::makescratch(int defonly)
+{
+	scratch.freeall();
+	stream cd;
+	for (cd = definite; cd.more(); cd.advance())
+		scratch.append(cd.current());
+	if (!defonly)
+		for (cd = *stage; cd.more(); cd.advance())
+			if (cd.current()->numcol() == 1)
+				scratch.append(cd.current());
+	if (twocol->nonempty())
+		scratch.append(twocol);
+}
+
+// Accept the current contents of the stage.
+// If the stage contains two-column ranges, add a sentinel to indicate the end
+// of a chunk of stage contents.
+void page::commit()
+{
+	if (dbg & 4)
+		printf("#entering page::commit()\n");
+	int numcol = 0;
+	while (stage->more()) {
+		numcol = stage->current()->numcol();
+		adddef(stage->dequeue());
+	}
+	if (numcol == 2)
+		adddef(new sentrange);
+}
+
+// Send the current contents of the stage back to its source.
+void page::welsh()
+{
+	if (dbg & 4)
+		printf("#entering page::welsh()\n");
+	while (stage->more()) {
+		range *r = stage->dequeue();
+		r->enqueue(ANDBLOCK);
+	}
+}
+
+enum { USonly = 1 };
+
+// So long as anything is eligible to go onto the page, keep trying.
+// Once nothing is eligible, compose and justify the page.
+void page::fill()
+{
+	while (stage->prime())
+		stage->pend();
+	compose(FINAL);
+	if (dbg & 16)
+		scratch.dump();
+	if (anymore()) {
+		int adjht = scratch.height();
+		if (adjht > minfull*pagesize) {
+			justify(&scratch, pagesize);
+			adjht = scratch.height();
+			int stretchamt = max(pagesize - adjht, 0);
+			twocol->stretch(twocol->height() + stretchamt);
+					// in case the page's stretchability lies
+					// entirely in its two-column part
+		} else
+			ERROR "page %d only %.0f%% full; will not be adjusted\n",
+				userpn, 100*(double) adjht/pagesize WARNING;
+	}
+}
+
+void page::adddef(range *r)
+{
+	if (dbg & 4)
+		printf("#entering page::adddef()\n");
+	switch (r->numcol()) {
+	case 1:	definite.append(r);
+		break;
+	case 2: twocol->definite.append(r);
+		break;
+	default: ERROR "%d-column range unexpected\n", r->numcol() FATAL;
+	}
+}
+
+int multicol::print(int cv, int col)
+{
+	if (col != 0)
+		ERROR "multicolumn output must start in left column\n" FATAL;
+	int curv = cv, maxv = cv;	// print left column
+	for ( ; column[0].more(); column[0].advance()) {
+		curv = column[0].current()->print(curv, 0);
+		maxv = max(maxv, curv);
+	}
+	curv = cv;			// print right column
+	for ( ; column[1].more(); column[1].advance()) {
+		curv = column[1].current()->print(curv, 1);
+		maxv = max(maxv, curv);
+	}
+	return maxv;
+}
+
+void page::print()
+{
+	static int tops = 1, bots = 1;
+	if (!scratch.more()) {
+		ERROR "## Here's what's left on squeue:\n" WARNING;
+		squeue.dump();
+		ERROR "## Here's what's left on bfqueue:\n" WARNING;
+		bfqueue.dump();
+		ERROR "## Here's what's left on ufqueue:\n" WARNING;
+		ufqueue.dump();
+		ERROR "page %d appears to be empty\n", userpn WARNING;
+		fflush(stderr), fflush(stdout), exit(0);
+					// something is very wrong if this happens
+	}
+	printf("p%d\n", userpn);	// print troff output page number
+	if (ptlist.more()) {		// print page header
+		ptlist.current()->print(0, 0);
+		ptlist.advance();
+	} else if (tops) {
+		ERROR "ran out of page titles at %d\n", userpn WARNING;
+		tops = 0;
+	}
+	int curv = 0;
+	printf("V%d\n", curv = pagetop);// print page contents
+	for ( ; scratch.more(); scratch.advance()) {
+		curv = scratch.current()->print(curv, 0);
+	}
+	if (btlist.more()) {		// print page footer
+		btlist.current()->print(0, 0);
+		btlist.advance();
+	} else if (bots) {
+		ERROR "ran out of page bottoms at %d\n", userpn WARNING;
+		bots = 0;
+	}
+	printf("V%d\n", physbot);	// finish troff output page
+}
+
+int	pagetop	= 0;		// top printing margin
+int	pagebot = 0;		// bottom printing margin
+int	physbot = 0;		// physical bottom of page
+
+double minfull = 0.9;		// minimum fullness before padding
+
+int	pn	= 0;		// cardinal page number
+int	userpn	= 0;		// page number derived from PT slugs
+
+static void makepage()
+{
+	page pg(pagebot - pagetop);
+	++pn;
+	userpn = ptlist.more() ? ptlist.current()->pn() : pn;
+	pg.fill();
+	pg.print();
+}
+
+static void conv(FILE *fp)
+{
+	startup(fp);		// read slugs, etc.
+	while (anymore())
+		makepage();
+	lastrange->print(0, 0);	// trailer
+	checkout();		// check that everything was printed
+}
+
+int
+main(int argc, char **argv)
+{
+	static FILE *fp = stdin;
+	progname = argv[0];
+	while (argc > 1 && argv[1][0] == '-') {
+		switch (argv[1][1]) {
+		case 'd':
+			dbg = atoi(&argv[1][2]);
+			if (dbg == 0)
+				dbg = ~0;
+			break;
+		case 'm':
+			minfull = 0.01*atof(&argv[1][2]);
+			break;
+		case 'c':
+			coltol = 0.01*atof(&argv[1][2]);
+			break;
+		case 'w':
+			wantwarn = 1;
+			break;
+		}
+		argc--;
+		argv++;
+	}
+	if (argc <= 1)
+		conv(stdin);
+	else
+		while (--argc > 0) {
+			if (strcmp(*++argv, "-") == 0)
+				fp = stdin;
+			else if ((fp = fopen(*argv, "r")) == NULL)
+				ERROR "can't open %s\n", *argv FATAL;
+			conv(fp);
+			fclose(fp);
+		}
+	exit(0);
+	return 0;	/* gcc */
+}
blob - /dev/null
blob + dcccbe0c9e0e12d8f064a58e49919c567681294a (mode 644)
--- /dev/null
+++ src/cmd/mpm/page.h
@@ -0,0 +1,119 @@
+extern queue	squeue;			// the three queues on which ranges reside
+extern queue	bfqueue;
+extern queue	ufqueue;
+
+extern double minfull;
+
+extern double coltol;
+
+int anymore();
+
+// The following is used in some calls to range::enqueue(int = 0).
+#define ANDBLOCK 1
+
+class page;
+
+enum { DRAFT = 0, FINAL = 1 };
+
+// The mergestream currpage->stage serves as a staging area for page makeup:
+// when primed, it contains a minimal acceptable chunk of input ranges.
+// The page must either take or leave everything that's on stage.
+class mergestream : public queue {
+	page	*currpage;		// current page that's accepting stuff
+  public:
+	mergestream(page *cp)	{ currpage = cp; unblock(); }
+	void	unblock();
+	int	prime();		// stage next legal chunk
+	void	pend();			// process pending chunk on stage
+};
+
+// The multicol currpage->twocol is the two-column piece of the page to which
+// two-column ranges are currently being added.
+// The page sets htavail to indicate how tall it is allowed to become.
+// All ranges on definite must be placed when the multicol is printed.
+// Each of these definite ranges also resides on one of column[0] and [1],
+// which represent the current best guess about how to divide definite
+// between the two columns.
+class multicol : public range {
+	page	*currpage;		// current page that's accepting stuff
+	stream	definite;		// definitely on page
+	stream	scratch;		// for trial compositions
+	stream	column[2];		// left (0) and right (1) columns
+	int	leftblocked;		// OK to add to left column?
+	int	htavail;		// max possible ht, set by page::tryout()
+	int	prevhtavail;		// max 2-colht last time we added something
+	friend	class page;
+public:
+	multicol(page *cp)	{ currpage = cp;
+				leftblocked = 0;
+				htavail = 0;
+				prevhtavail = -1;
+				setgoal(NOGOAL); }
+					// the two-column piece behaves as part
+					// of the stream of single-column input.
+	int	numcol()	{ return 1; }
+	int	nonempty()	{ return definite.more(); }
+	void	choosecol(range *, int);// add first arg to one or other column
+	void	choosecol(stream*, int);// add *all ranges on first arg*
+					// to one or other column
+					// NOT the same as a mapcar of the
+					// preceding function over the ranges
+					// on the first argument!
+	void	compose(int);		// divide into two columns
+	void	tryout();		// decide which column gets stage contents
+	void	stretch(int);		// justify both columns to given height
+	int	print(int curv, int col);
+	int	height();		// an upper bound on actual height
+	int	rawht()		{ return max(column[0].rawht(), column[1].rawht()); }
+	void	reheight(int *cv, int *mv)
+				{ *cv += height(); *mv = max(*mv, *cv); }
+	void	dump();
+	int	isvbox()	{ return nonempty(); }	// during trimspace()
+};
+
+// These sentinel ranges are used to separate the ranges on twocol::definite
+// into the chunks in which they came from the staging area.
+// Thus, they preserve the results of the computation that was done to prime
+// page::stage.
+class sentrange : public range {
+  public:
+	sentrange()		{ }
+	int	numcol()	{ return 2; }
+	int	issentinel()	{ return 1; }
+};
+
+class page {
+	int	pagesize;		// allowed maximum height
+	int	prevncol;		// was last item tried 1- or 2-column?
+	int	vsince;			// how many vboxes from "current" BS
+					// (to avoid putting a single line on
+					// a page with a very large floatable)
+	stream	definite;		// definitely on page, in input order
+	stream	scratch;		// playground in which to alter page
+	void	cmdproc();		// process any of several commands
+	void	parmproc();		// process any of several parameters
+	void	tryout();		// see whether current stage contents fit
+	void	compose(int);		// float and trim current page contents
+	void	makescratch(int);	// fill scratch area
+	void	commit();		// accept the items on stage
+	void	welsh();		// reject the items on stage
+	void	adddef(range *r);	// add to one of the definite queues
+					// (definite or twocol->definite)
+  public:
+	mergestream *stage;
+	friend	class mergestream;
+	multicol *twocol;
+	friend class multicol;
+	page(int p)	{ pagesize = p;
+			prevncol = 1;
+			vsince = 0;
+			stage = new mergestream(this);
+			twocol = new multicol(this); }
+	~page()	{ definite.freeall(); scratch.freeall(); }
+	void	fill();
+	int	blank()	{ return !definite.more() && !twocol->definite.more();}
+	void	print();
+};
+
+// functions in page.c
+int main(int, char **);
blob - /dev/null
blob + d065ac827c81c8738e28ac73add1e1316985a12f (mode 644)
--- /dev/null
+++ src/cmd/mpm/queue.cc
@@ -0,0 +1,235 @@
+#include	"misc.h"
+#include	"slug.h"
+#include	"range.h"
+#include	"page.h"
+
+queue	squeue;
+queue	bfqueue;
+queue	ufqueue;
+
+// We use the stream function current() to access a queue's head.
+// Thus, queue member curr should always point to its first range.
+void queue::check(char *whence)
+{
+	if (dbg & 8) {
+		char *p;
+		if (this == &squeue)
+			p = "squeue";
+		else if (this == &bfqueue)
+			p = "bfqueue";
+		else if (this == &ufqueue)
+			p = "ufqueue";
+		else
+			p = "weird queue";
+		printf("#checking %s\n", p);
+	}
+	if (first != curr)
+		ERROR "check(%s): first != curr, line %d\n", whence, curr->rp->lineno() FATAL;
+}
+
+// When ranges are told to enqueue themselves, they are being rejected from the
+// stage back onto their original queues.
+// They reset any parameters that may have been altered by staging or trial
+// composition.
+
+void	range::enqueue(int block)
+{
+	squeue.enqueue(this);
+	if (block)
+		squeue.block();
+}
+
+void	ufrange::enqueue(int block)
+{
+	restore();			// both goal positions
+	ufqueue.enqueue(this);
+	if (block)
+		ufqueue.block();
+}
+
+void	bfrange::enqueue(int block)
+{
+	restore();			// both goal positions
+	bfqueue.enqueue(this);
+	if (block)
+		bfqueue.block();
+}
+
+int anymore()
+{
+	return !(squeue.empty() && ufqueue.empty() && bfqueue.empty());
+}
+
+void mergestream::unblock()
+{
+	squeue.unblock();
+	bfqueue.unblock();
+	ufqueue.unblock();
+}
+
+// Fill the staging area with a minimal chunk of input ranges.
+int mergestream::prime()
+{
+	if (dbg & 4)
+		printf("#entering mergestream::prime()\n");
+	if (!empty())
+		return 1;
+	int brkok = 1;			// is it OK to break after the last
+					// VBOX that was added to the stage?
+	int needheight = -1;		// minimum acceptable height of the
+					// chunk being constructed on stage
+	// If the range at the head of any queue is breaking,
+	// deal with it first.
+	if (squeue.more() && squeue.current()->breaking())
+		enqueue(squeue.dequeue());
+	else if (bfqueue.more() && (bfqueue.current()->breaking() ||
+		(bfqueue.serialno() < squeue.serialno())))
+		enqueue(bfqueue.dequeue());
+	else if (ufqueue.more() && (ufqueue.current()->breaking() ||
+		(ufqueue.serialno() < squeue.serialno())))
+		enqueue(ufqueue.dequeue());
+	else while (squeue.more()) {
+		// Fill the stage with enough ranges to be a valid chunk.
+		range *r = squeue.dequeue();
+		if (r->isvbox()) {	// VBOX
+			if (dbg & 16)
+				printf("#VBOX: !empty: %d; brkok: %d; vsince: %d\n",
+					!empty(), brkok, currpage->vsince);
+			if (!empty()	// there's something there
+				&& brkok
+					// it's OK to break here
+				&& currpage->vsince >= 2
+					// enough stream has gone onto this page
+				&& rawht() >= needheight
+					// current need has been satisfied
+				) {
+					// the stage already contains enough
+					// ranges, so this one can wait
+				r->enqueue();
+				break;
+			} else {
+				if (r->rawht() > 0) {
+					++currpage->vsince;
+					brkok = r->brkafter();
+				}
+				enqueue(r);
+			}
+		} else if (r->isnested() || r->issp()) {	// US, SP
+			if (!empty() && rawht() >= needheight) {
+					// enough already, wait
+				r->enqueue();
+				break;
+			}
+			currpage->vsince = 0;
+			enqueue(r);
+			if (height() >= needheight)
+				break;
+		} else if (r->isneed()) {	// NE
+			if (!empty() && rawht() >= needheight) {
+					// not currently working on an unsatisfied NEed 
+				r->enqueue();
+				break;
+			}
+					// deal with overlapping NEeds
+			needheight = rawht() + max(needheight - rawht(), r->needht());
+			enqueue(r);
+		} else if (r->forceflush() == NO) {
+			enqueue(r);
+		} else if (r->forceflush() == YES) {
+			currpage->vsince = 0;
+			if (!empty()) {
+					// ready or not, r must wait
+				r->enqueue();
+				break;
+			}
+			enqueue(r);
+			break;
+		} else
+			ERROR "unexpected  %s[%s] in prime(), line %d\n",
+				r->typename(), r->headstr(), r->lineno() FATAL;
+	}
+	return more();			// 0 if nothing was staged
+}
+
+void page::cmdproc()
+{
+	if (stage->next())
+		ERROR "more than a single command on bsqueue\n" FATAL;
+	switch (stage->current()->cmdtype()) {
+	case FC:	// freeze the current 2-column range and start a new one
+		adddef(stage->dequeue());
+		twocol->compose(FINAL);
+		adddef(twocol);
+		twocol = new multicol(this);
+		break;
+	case BP:	// force a page break
+		adddef(stage->dequeue());
+		squeue.block();
+		break;
+	case FL:	// flush out all floatables that precede this range:
+			// no more stream input allowed until they're past
+		if (stage->serialno() > ufqueue.serialno() ||
+			stage->serialno() > bfqueue.serialno()) {
+			range *r = stage->dequeue();
+			r->enqueue(ANDBLOCK);
+		} else
+			adddef(stage->dequeue());
+		break;
+	default:
+		stage->current()->dump();
+		ERROR "unknown command\n" FATAL;
+	}
+}
+
+void page::parmproc()
+{
+	if (stage->next())
+		ERROR "more than a single parameter on bsqueue\n" FATAL;
+	switch (stage->current()->parmtype()) {
+	case NP:	// page top margin
+		if (blank())
+			pagetop = stage->current()->parm();
+		pagesize = pagebot - pagetop;
+		break;
+	case FO:
+		if (blank())
+			pagebot = stage->current()->parm();
+		pagesize = pagebot - pagetop;
+		break;
+	case PL:
+		if (blank())
+			physbot = stage->current()->parm();
+		break;
+	case MF:
+		minfull = 0.01*stage->current()->parm();
+		break;
+	case CT:
+		coltol = 0.01*stage->current()->parm();
+		break;
+	case WARN:
+		wantwarn = stage->current()->parm();
+		break;
+	case DBG:
+		dbg = stage->current()->parm();
+		break;
+	default:
+		stage->current()->dump();
+		ERROR "unknown parameter\n" FATAL;
+	}
+	adddef(stage->dequeue());
+}
+
+// Process the contents of the staging area; a relic that used to do more.
+void mergestream::pend()
+{
+	if (dbg & 4)
+		printf("#entering mergestream::pend()\n");
+	if (!more())
+		return;
+	if (current()->iscmd())
+		currpage->cmdproc();
+	else if (current()->isparm())
+		currpage->parmproc();
+	else
+		currpage->tryout();
+}
blob - /dev/null
blob + 988dff2f28160a5e6e39481faaab2334d9defbf9 (mode 644)
--- /dev/null
+++ src/cmd/mpm/range.cc
@@ -0,0 +1,613 @@
+#include	<math.h>
+#include	"misc.h"
+#include	"slug.h"
+#include	"range.h"
+
+void sprange::reheight(int *cv, int *mv)
+{
+	if (*cv != *mv)
+		ERROR "slug %d: an imbedded SP, line %d\n",
+			first->serialno(), first->lineno() WARNING;
+	*cv += dv;
+	*mv = max(*mv, *cv);
+}
+
+void sprange::rerawht(int *cv, int *mv)
+{
+	*cv += rawht();
+	*mv = max(*mv, *cv);
+}
+
+void nestrange::restore()
+{
+	subrange->restoreall();
+}
+
+void stream::freeall()	// not a destructor;  called explicitly
+{
+	strblk *p, *q;
+	for (p = first; p; p = q) {
+		q = p->next;
+		delete p;
+	}
+	first = last = curr = 0;
+}
+
+void stream::dump()
+{
+	for (stream s = *this; s.more(); s.advance())
+		s.current()->dump();
+}
+
+void stream::rdump()
+{
+	for (stream s = *this; s.more(); s.advance())
+		s.current()->rdump();
+}
+
+int stream::restoreall()
+{
+	for (stream s = *this; s.more(); s.advance())
+		s.current()->restore();
+	return measure(this);
+}
+
+range *stream::append(range *r)
+{
+	if (last == 0)
+		curr = first = last = new strblk;
+	else {
+		last->next = new strblk;
+		last = last->next;
+		if (curr == 0)
+			curr = last;
+	}
+	last->next = 0;
+	return last->rp = r;
+}
+
+void stream::split()	// duplicate current() range
+{
+	strblk *s2 = new strblk;
+	range *r2 = curr->rp->clone();
+	s2->rp = r2;
+	s2->next = curr->next;
+	if (last == curr)
+		last = s2;
+	curr->next = s2;
+	curr->rp->killkids();		// children only in the 2nd one
+	// r2->crosslink(r1);
+}
+
+int stream::height()
+{
+	int h;
+	stream s = *this;
+	for (h = 0; s.more(); s.advance())
+		h += s.current()->height();
+	return h;
+}
+
+int stream::rawht()
+{
+	int h;
+	stream s = *this;
+	for (h = 0; s.more(); s.advance())
+		h += s.current()->rawht();
+	return h;
+}
+
+int measure(stream *sp)		// record high-water mark of stream
+{				// sets nested stream heights
+	stream s = *sp;
+	int curv, maxv;
+	for (maxv = curv = 0; s.more(); s.advance())
+		s.current()->reheight(&curv, &maxv);
+	return maxv;
+}
+
+int rawmeasure(stream *sp)
+{
+	stream s = *sp;
+	int curv, maxv;
+	for (maxv = curv = 0; s.more(); s.advance())
+		s.current()->rerawht(&curv, &maxv);
+	return maxv;
+}
+
+void nestrange::rdump()
+{
+	dump();
+	if (subrange)
+		subrange->rdump();
+}
+
+void nestrange::killkids()
+{
+	subrange = new stream;
+}
+
+int nestrange::print(int curv, int col)
+{
+	int ocurv = curv;
+	first->slugout(col);
+	for (stream s = *subrange; s.more(); s.advance())
+		curv = s.current()->print(curv, col);
+	return ocurv + height();
+}
+
+#define macroclone(rangetype) range *rangetype::clone() {\
+	rangetype *t = new rangetype;\
+	*t = *this;\
+	return t; }
+
+macroclone(usrange);
+macroclone(ufrange);
+macroclone(bfrange);
+
+#undef macroclone
+
+#define macropickgoal(rangetype) void rangetype::pickgoal(int acv, double scale) {\
+	if (scale > 1) {\
+		goalV = (int)(scale*goalV);\
+		goal2 = (int)(scale*goal2);\
+	}\
+	if (abs(acv - goalV) > abs(acv-goal2))\
+		goalV = goal2; }
+
+macropickgoal(ufrange)
+macropickgoal(bfrange)
+
+#undef macropickgoal
+
+range *generator::next()
+{
+	range *r;
+	if (child) {
+		if ((r = child->next()) != 0)
+			return r;
+		delete child;
+		child = 0;
+	}
+	if (!s.more())
+		return 0;
+	r = s.current();
+	if (r->isnested())
+		child = new generator(r->children());
+	s.advance();
+	return r;
+}
+
+range *queue::enqueue(range *r)
+{
+	if (dbg & 8)
+		printf("#entering queue::enqueue()\n");
+	check("queue::enqueue");
+	if (!last || last->rp->serialno() < r->serialno())	// common case
+		return append(r);
+	if (dbg & 8)
+		printf("#queue::enqueue() pushing back\n");
+	newguy = new strblk;
+	newguy->rp = r;
+	if (r->serialno() < first->rp->serialno()) {
+		newguy->next = first;
+		curr = first = newguy;
+		return newguy->rp;
+	}
+	if (dbg & 8)
+		printf("#queue::enqueue() searching down queue\n");
+	for (curr = first;
+		next() && next()->serialno() < r->serialno();
+		curr = curr->next)
+		;
+	newguy->next = curr->next;
+	curr->next = newguy;
+	curr = first;			// restore important queue condition
+	return newguy->rp;
+}
+
+range *queue::dequeue()
+{
+	if (dbg & 8)
+		printf("#entering queue::dequeue()\n");
+	check("queue::dequeue");
+	curr = first->next;
+	range *retval = first->rp;
+	delete first;
+	first = curr;
+	if (!curr)
+		last = 0;
+	return retval;
+}
+
+// ================================================================================
+
+//	functions that munge the troff output stored in slugs[]
+
+// ================================================================================
+
+static void doprefix(FILE *fp) // copy 1st "x" commands to output
+{
+	int c;
+
+	while ((c = getc(fp)) != EOF) {
+		if (c != 'x') {
+			ungetc(c, fp);
+			break;
+		}
+		putchar(c);
+		do {
+			putchar(c = getc(fp));
+		} while (c != '\n');
+		linenum++;
+	}
+//	printf("x font 1 R\n");	// horrible kludge: ensure a font for first f1 command 
+}
+
+#define	DELTASLUGS	15000
+
+static slug	*slugs = 0;
+static int	nslugs = 0;	// slugs has nslugs slots
+static slug	*slugp = 0;	// next free slug in slugs
+
+static void readslugs(FILE *fp)
+{
+	if ((slugs = (slug *) malloc((nslugs = DELTASLUGS)*sizeof(slug))) == NULL)
+		ERROR "no room for %d-slug array\n", nslugs FATAL;
+	slugp = slugs;
+	for (slugp = slugs; ; slugp++) {
+		if (slugp >= slugs+nslugs-2) {
+			int where = slugp - slugs;
+			if ((slugs = (slug *) realloc((char *) slugs, (nslugs += DELTASLUGS)*sizeof(slug))) == NULL)
+				ERROR "no room for %d slugs\n", nslugs FATAL;
+			ERROR "now slug array can hold %d slugs\n", nslugs WARNING;
+			slugp = slugs + where;
+		}
+		*slugp = getslug(fp);
+		if (slugp->type == EOF)
+			break;
+	}
+	*++slugp = eofslug();
+	printf("# %d slugs\n", slugp-slugs);
+}
+
+static slug *findend(slug *sp)
+{
+	slug *p;
+	for (p = sp; p->type == sp->type; p++)	// skip runs
+		;				// espec UF UF UF 
+	for ( ; p < slugp; p++)
+		switch (p->type) {
+		case US:
+		case UF:
+		case BF:
+		case PT:
+		case BT:
+			p = findend(p);
+			break;
+		case END:
+			return p;
+		}
+	ERROR "walked past EOF in findend looking for %d (%s), line %d\n",
+		sp->type, sp->typename(), sp->lineno() FATAL;
+	return sp;
+}
+
+static int markp(int i, int n, int parm)
+{	// should VBOX i of n be marked to brevent breaking after it?
+	if (i >= n-1)
+		return 0;
+	return i <= parm-2 || i >= n-parm;
+}
+
+static void markbreak(slug *p)
+{
+	// Mark impermissible breakpoints in BS's.
+	// The parm field of a VBOX is >0 if we shouldn't break after it.
+	int parm;		// how many lines must stay on page
+	int goahead = 1;	// true until we see the next BS
+	int nowmark = 0;	// true when we should be marking
+	int n = 0;
+	while (p->type == BS)
+		parm = p++->parm;	// latest BS parm applies
+	slug *op = p;
+	while (goahead) {
+		switch (p->type) {
+		case VBOX:		// count VBOXes so second pass knows
+			if (p->dv > 0)	// knows how far to end of BS
+				n++;
+			break;
+		case US:		// mark around EQ/EN, etc.
+			nowmark = 1;
+			p = findend(p);
+			break;
+		case UF:		// but not around floats, PTs, and BTs
+		case BF:
+		case PT:
+		case BT:
+			p = findend(p);
+			break;
+		case SP:		// naked SP:  probable macro botch
+			nowmark = 1;	// mark around it anyhow
+			break;
+		case BS:		// beginning of next paragraph
+		case END:		// probable macro botch
+		case EOF:
+			goahead = 0;	// stop work after marking
+			nowmark = 1;
+		default:
+			break;
+		}
+		p++;
+		if (nowmark) {
+			int i = 0;	// VBOX counter for second pass
+			while (op < p) {
+				switch (op->type) {
+				case VBOX:
+					if (op->dv > 0)
+						op->parm = markp(i, n, parm);
+					i++;
+					break;
+				case US:	// caused second pass to begin
+				case SP:
+				case BS:
+				case END:
+				case EOF:
+					op = p;
+					break;
+				case UF:	// skip on this pass too
+				case BF:
+				case PT:
+				case BT:
+					op = findend(op);
+					break;
+				default:
+					break;
+				}
+			op++;
+			}
+			if (i != n)
+				ERROR "markbreak failed : i %d n %d\n",
+					i, n WARNING;
+			op = p;
+			nowmark = n = 0;
+		}
+	}
+}
+
+static void fixslugs()		// adjust bases and dv's, set parameters, etc.
+{
+	slug *p, *prevV = 0;
+	for (p = slugs; p < slugp; p++) {
+		if (p->type == VBOX) {
+			prevV = p;
+			continue;
+		}
+		if (p->base != 0) {
+			ERROR "%s slug (type %d) has base = %d, line %d\n",
+				p->typename(), p->type, p->base, p->lineno() WARNING;
+		}
+		if ((p->type == SP) || (p->type == NE))
+			continue;
+		if (p->type == PAGE)
+			prevV = 0;
+		if (p->dv != 0)
+			if (prevV) {
+				prevV->base = max(prevV->base, p->dv);
+				p->dv = 0;
+			} else {
+				ERROR "%s slug (type %d) has dv = %d, line %d\n",
+					p->typename(), p->type, p->dv, p->lineno() WARNING;
+			}
+	}
+	prevV = 0;
+	int firstNP = 0, firstFO = 0, firstPL = 0;
+	for (p = slugs; p < slugp; p++) {
+		switch (p->type) {
+		// adjust the dv in a sequence of VBOXes
+		// by subtracting from each the base of the preceding VBOX
+		case VBOX:
+			if (prevV)
+				p->dv -= prevV->base;
+			prevV = p;
+			break;
+		case SP:
+			p->dv = max(p->dv, 0);
+			break;
+		case PAGE:
+			p->neutralize();
+			prevV = 0;
+			break;
+		// record only first "declarations" of Page Top and bottom (FO);
+		case PARM:
+			switch (p->parm) {
+			case NP:
+				if (firstNP++ == 0)
+					pagetop = p->parm2;
+				p->neutralize();
+				break;
+			case FO:
+				if (firstFO++ == 0)
+					pagebot = p->parm2;
+				p->neutralize();
+				break;
+			case PL:
+				if (firstPL++ == 0)
+					physbot = p->parm2;
+				p->neutralize();
+				break;
+			}
+			break;
+		// things that begin groups; not US, which should nest properly
+		case UF:
+		case BF:
+			while ((p+1)->type == p->type) {
+							// join adjacent identical
+				(p+1)->parm2 = p->parm;	// parm is latest
+							// parm2 is previous
+				p->neutralize();	// so it's not seen later
+				p++;
+			}
+			break;
+		// none of the above
+		case US:
+		case PT:
+		case BT:
+		case BS:
+		case END:
+		case TM:
+		case COORD:
+		case NE:
+		case MC:
+		case CMD:
+		case EOF:
+			break;
+		default:
+			ERROR "Unknown slug type %d in fixslugs, line %d\n",
+				p->type, p->lineno() WARNING;
+			break;
+		}
+	}
+	int pagesize = pagebot - pagetop;
+	if (pagesize == 0)
+		ERROR "Page dimensions not declared\n" FATAL;
+	if (physbot == 0)
+		physbot = pagebot + pagetop;
+	printf("# page top %d bot %d size %d physbot %d\n",
+		pagetop, pagebot, pagesize, physbot);
+	for (p = slugs; p < slugp; p++) {
+		switch (p->type) {
+		// normalize float parameters
+		case BF:
+		case UF:
+					// primary goal
+			p->parm = max(min(p->parm-pagetop, pagesize), 0);
+					// secondary goal
+			p->parm2 = max(min(p->parm2-pagetop, pagesize), 0);
+			break;
+		// normalize need parameters
+		case NE:
+			p->dv = max( min(p->dv, pagesize), 0);
+			break;
+		// mark permissible breaks
+		case BS:
+			markbreak(p);
+			break;
+		}
+		if (dbg & 1)
+			p->dump();
+	}
+}
+
+void checkout()
+{
+	for (slug *p = slugs; p < slugp; p++)
+		switch (p->type) {
+		case PT:
+		case BT:
+			p = findend(p);
+			break;
+		case SP:
+		case VBOX:
+			if (p->seen != 1)
+				ERROR "%s slug %d seen %d times\n",
+					p->typename(), p->serialno(),
+					p->seen WARNING;
+			break;
+		}
+}
+
+eofrange *lastrange;
+stream	ptlist, btlist;
+
+static slug *makeranges(slug *p, stream *s, int level)
+{
+	stream *t;
+
+	for ( ; p < slugp; p++)
+		switch (p->type) {
+		case VBOX:
+			s->append(new vboxrange(p));
+			break;
+		case SP:
+			s->append(new sprange(p));
+			break;
+		case BS:
+			s->append(new bsrange(p));
+			break;
+		case US:
+			s->append(new usrange(p, t = new stream));
+			p = makeranges(p+1, t, level+1);
+			break;
+		case BF:
+			s->append(new bfrange(p, t = new stream));
+			p = makeranges(p+1, t, level+1);
+			break;
+		case UF:
+			s->append(new ufrange(p, t = new stream));
+			p = makeranges(p+1, t, level+1);
+			break;
+		case PT:
+			ptlist.append(new ptrange(p, t = new stream));
+			p = makeranges(p+1, t, level+1);
+			break;
+		case BT:
+			btlist.append(new btrange(p, t = new stream));
+			p = makeranges(p+1, t, level+1);
+			break;
+		case END:
+			s->append(new endrange(p));
+			return p;
+		case TM:
+			s->append(new tmrange(p));
+			break;
+		case COORD:
+			s->append(new coordrange(p));
+			break;
+		case NE:
+			if (level) {
+				ERROR "Nested NE commands are ignored, line %d\n",
+					p->lineno() WARNING;
+				p->dv = 0;
+			}
+			s->append(new nerange(p));
+			break;
+		case MC:
+			s->append(new mcrange(p));
+			break;
+		case CMD:
+			if (level)
+				ERROR "Nested command ignored, line %d\n",
+					p->lineno() WARNING;
+			s->append(new cmdrange(p));
+			break;
+		case PARM:
+			if (level)
+				ERROR "Nested parameter ignored, line %d\n",
+					p->lineno() WARNING;
+			s->append(new parmrange(p));
+			break;
+		case EOF:
+			lastrange = new eofrange(p);
+			return 0;
+		}
+	return p;
+}
+
+static queue text;			// unexamined input ranges; the real data
+
+void startup(FILE *fp)
+{
+	doprefix(fp);			// peel off 'x' commands
+	readslugs(fp);			// read everything into slugs[]
+	fixslugs();			// measure parameters and clean up
+	makeranges(slugs, &text, 0);	// add range superstructure
+	measure(&text);		// heights of nested things
+	rawmeasure(&text);
+	while (text.more()) {
+		range *r = text.dequeue();
+		if (dbg & 2)
+			r->dump();
+		r->enqueue();
+	}
+}
blob - /dev/null
blob + 401035a52d54d51a11be0ece4323f194ad376d4f (mode 644)
--- /dev/null
+++ src/cmd/mpm/range.h
@@ -0,0 +1,334 @@
+const int	NOGOAL = -1;
+
+class stream;
+
+enum primeflush { NO, YES, EXPECTED, UNEXPECTED };	// mergestream::prime()
+
+// Ranges do two things.  They interpose a layer between slugs and the rest
+// of the program; this is important because of the grossness of the slug
+// data structure (made necessary by its origins in troff output).  Ranges also
+// group together other ranges into meaningful chunks like unbreakable stream
+// objects, floatable objects, and page headers and footers.
+// Member function height() returns a range's height as of the latest composition.
+// Member function rawht() returns the range's original height in the input.
+class range {
+  protected:
+	slug	*first;		// earliest slug in range
+	int	accumV;		// accumulated V to this point
+  public:
+	range()		{ first = 0; accumV = 0; }
+	range(slug *p)	{ first = p; accumV = 0; }
+	char	*headstr()		{
+		return first ? first->headstr() : (char*)""; }
+	char	*typename()		{ return first->typename(); }
+	int	serialno()		{ return first->serialno(); }
+	int	lineno()		{ return first->lineno(); }
+	virtual void	dump()		{ first->dump(); }
+	virtual void	rdump()		{ dump(); }
+	virtual int	print(int cv, int col)	{
+		first->slugout(col); return cv; }
+	virtual int	floatable()	{ return 0; }
+	virtual int	brkafter()	{ return 1; }
+	virtual int	isnested()	{ return 0; }
+	virtual int	issp()		{ return 0; }
+	virtual int	isvbox()	{ return 0; }
+	virtual int	isneed()	{ return 0; }
+	virtual int	iscmd()		{ return 0; }
+	virtual int	cmdtype()	{ return -1; }
+	virtual int	isparm()	{ return 0; }
+	virtual int	parmtype()	{ return -1; }
+	virtual int	parm()		{ return -1; }
+	virtual int	breakable()	{ return 0; }
+	virtual int	forceflush()	{ return UNEXPECTED; }
+	virtual int	pn()		{ return 0; }
+	virtual stream	*children()	{ return 0; }	// see page::peeloff()
+	virtual void	killkids()	{ }
+	virtual void	enqueue(int = 0);
+	virtual int	height()	{ return 0; }
+	virtual int	rawht()		{ return 0; }
+	virtual int	needht()	{ return 0; }
+	virtual void	reheight(int *, int *)	{ }
+	virtual void	rerawht(int *, int *)	{ }
+	virtual void	setheight(int) { }
+	virtual void	restore()	{ }		// goals of floatables
+	virtual int	goal()		{ return NOGOAL; }
+	int		accum()		{ return accumV; }
+	void		setaccum(int n)	{ accumV = n; }
+	virtual	void	setgoal(int)	{ }
+	virtual void	pickgoal(int, double)	{ }
+	virtual int	numcol()	{ return first->numcol(); }
+	virtual int	issentinel()	{ return 0; }
+	virtual range	*clone()	{ return 0; }
+	virtual int	breaking()	{ return 0; }
+	virtual void	setbreaking()	{ }
+};
+
+class vboxrange : public range {
+	int	dv;		// inherited from slug
+	int	base;		// inherited from slug
+	int	brk;		// 0 => ok to break after, 1 => no break 
+  public:
+	vboxrange(slug *p) : range(p) { dv = p->dv; base = p->base; brk = p->parm; }
+	void	dump() {
+		printf("#### VBOX brk? %d dv %d ht %d\n", brk, dv, dv+base); }
+	int	print(int cv, int col) {
+		printf("V%d\n", cv += dv); first->slugout(col); return cv+base; }
+	int	brkafter()		{ return !brk; }
+	int	isvbox()		{ return 1; }
+	int	forceflush()		{ return NO; }
+	int	height()		{ return dv + base; }
+	int	rawht()			{ return first->dv + first->base; }
+	void	reheight(int *cv, int *mv) {
+		*cv += dv+base; *mv = max(*mv, *cv); }
+	void	rerawht(int *cv, int *mv) {
+		*cv += rawht(); *mv = max(*mv, *cv); }
+};
+
+class sprange : public range {
+	int dv;
+  public:
+	sprange(slug *p) : range(p) { dv = first->dv; }
+	void	dump() {
+		printf("#### SP dv %d (originally %d)\n", dv, first->dv); }
+	int	print(int cv, int col)	{
+		first->slugout(col); return cv + dv; }
+	int	issp()			{ return 1; }
+	int	forceflush()		{ return YES; }
+	int	height()		{ return dv; }
+	int	rawht()			{ return first->dv; }
+	void	reheight(int *, int *);
+	void	rerawht(int *, int *);
+	void	setheight(int n)	{ dv = n; }
+};
+
+class tmrange : public range {
+  public:
+	tmrange(slug *p) : range(p)	{ }
+	int	forceflush()		{ return NO; }
+	int	print(int cv, int col)	{ first->slugout(col); return cv; }
+};
+
+class coordrange : public range {
+  public:
+	coordrange(slug *p) : range(p)	{ }
+	int	forceflush()		{ return NO; }
+	int	print(int cv, int col)
+		{ first->slugout(col); printf(" Y %d\n", cv); return cv; }
+};
+
+class nerange : public range {
+  public:
+	nerange(slug *p) : range(p)	{ }
+	int	isneed()		{ return 1; }
+	int	forceflush()		{ return YES; }
+	int	needht()		{ return first->dv; }
+};
+
+class mcrange : public range {
+  public:
+	mcrange(slug *p) : range(p)	{ }
+	int	forceflush()		{ return YES; }
+};
+
+class cmdrange : public range {
+  public:
+	cmdrange(slug *p) : range(p)	{ }
+	int	iscmd()			{ return 1; }
+	int	forceflush()		{ return YES; }
+	int	cmdtype()		{ return first->parm; }
+};
+
+class parmrange : public range {
+  public:
+	parmrange(slug *p) : range(p)	{ }
+	int	isparm()		{ return 1; }
+	int	forceflush()		{ return YES; }
+	int	parmtype()		{ return first->parm; }
+	int	parm()			{ return first->parm2; }
+};
+
+class bsrange : public range {
+  public:
+	bsrange(slug *p) : range(p)	{ }
+	int	forceflush()		{ return NO; }
+	int	print(int cv, int col)	{ first->slugout(col); return cv; }
+};
+
+class endrange : public range {
+  public:
+	endrange(slug *p) : range(p)	{ }
+	int	forceflush()		{ return UNEXPECTED; }
+};
+
+class eofrange : public range {
+  public:
+	eofrange(slug *p) : range(p)	{ }
+	int	forceflush()		{ return UNEXPECTED; }
+};
+
+extern eofrange *lastrange;	// the EOF block (trailer, etc.) goes here
+
+int measure(stream *);
+int rawmeasure(stream *);
+
+// A nestrange packages together a sequence of ranges, its subrange.
+// Other parts of the program reach in and alter the dimensions of
+// some of these ranges, so when the height of a range is requested
+// it is computed completely afresh.
+// (Note:  the alternative, of keeping around many copies of ranges
+// with different dimensions, was abandoned because of the difficulty
+// of ensuring that exactly one copy of each original range would be
+// output.)
+class nestrange : public range {
+  protected:
+	stream	*subrange;
+	int isbreaking;
+	int rawdv;
+  public:
+	nestrange() : range()	{ subrange = 0; isbreaking = 0; rawdv = -1; }
+	nestrange(slug *p, stream *s) : range(p)
+				{ subrange = s; isbreaking = 0; rawdv = -1; }
+	void	rdump();
+	virtual void restore();
+	stream	*children()	{ return subrange; }
+	void	killkids();
+	int	height()	{ return measure(subrange); }
+	int	rawht()		{ if (rawdv < 0 || isbreaking) rawdv = rawmeasure(subrange);
+					return rawdv; }
+	void	reheight(int *cv, int *mv) {
+			*mv += measure(subrange); *cv = max(*mv, *cv); }
+	void	rerawht(int *cv, int *mv) {
+			*mv += rawht(); *cv = max(*mv, *cv); }
+	int	isnested()	{ return 1; }
+	int	forceflush()	{ return EXPECTED; }
+	int	print(int cv, int col);
+	int	breaking()	{ return isbreaking; }
+	void	setbreaking()	{ isbreaking++; }
+};
+
+class usrange : public nestrange {
+  public:
+	usrange()	{ }
+	usrange(slug *p, stream *s) : nestrange(p, s) {}
+	void dump() { printf("#### US	dv %d\n", height()); }
+	range	*clone();
+};
+
+class ufrange : public nestrange {
+	int	goalV, goal2;
+  public:
+	ufrange()	{ }
+	ufrange(slug *p, stream *s) : nestrange(p, s) {
+		goalV = p->parm; goal2 = p->parm2; }
+	void 	dump() { printf("#### UF   dv %d goal %d goal2 %d\n",
+		height(), goalV, goal2); }
+	int	floatable()	{ return 1; }
+	void	enqueue(int = 0);
+	range	*clone();
+	int	goal()		{ return goalV; }
+	void	setgoal(int n)	{ goalV = goal2 = n; }
+	void	pickgoal(int acv, double scale);
+	void	restore()	{ goalV = first->parm; goal2 = first->ht; }
+};
+
+class bfrange : public nestrange {
+	int	goalV, goal2;
+  public:
+	bfrange()	{ }
+	bfrange(slug *p, stream *s) : nestrange(p, s) {
+		goalV = p->parm; goal2 = p->parm2; }
+	void 	dump() { printf("#### BF   dv %d goal %d goal2 %d\n",
+		height(), goalV, goal2); }
+	int	floatable()	{ return 1; }
+	void	enqueue(int = 0);
+	range	*clone();
+	int	goal()		{ return goalV; }
+	void	setgoal(int n)	{ goalV = goal2 = n; }
+	void	pickgoal(int acv, double scale);
+	void	restore()	{ goalV = first->parm; goal2 = first->parm2; }
+	int	breakable()	{ return 1; }	// can be broken
+};
+
+class ptrange : public nestrange {
+	int	pgno;
+  public:
+	int	pn()	{ return pgno; }
+	ptrange(slug *p, stream *s) : nestrange(p, s) { pgno = p->parm; }
+	void 	dump() { printf("#### PT   pgno %d dv %d\n", pgno, height()); }
+};
+
+class btrange : public nestrange {
+	int	pgno;
+  public:
+	btrange(slug *p, stream *s) : nestrange(p, s) { pgno = p->parm; }
+	void 	dump() { printf("#### BT   pgno %d dv %d\n", pgno, height()); }
+};
+
+// A stream is a sequence of ranges; we use this data structure a lot
+// to traverse various sequences that crop up in page-making.
+class stream {
+  protected:
+public:
+	struct strblk {		// ranges are linked by these blocks
+		strblk	*next;
+		range	*rp;
+	};
+	strblk	*first;
+	strblk	*last;
+	strblk	*curr;
+  public:
+	stream()		{ curr = last = first = 0; }
+	stream(range *r)	{ curr = last = first = new strblk;
+					last->rp = r; last->next = 0; }
+	void	freeall();	// note:  not a destructor
+	void	dump();		// top level
+	void	rdump();	// recursive
+	int	restoreall();
+	range	*current()	{ return curr->rp; }
+	range	*next()		{ return curr && curr->next ? curr->next->rp : 0; }
+	void	advance()	{ curr = curr->next; }
+	range	*append(range *r);
+	void	split();
+	int	more()		{ return curr && curr->rp; }
+	int	height();
+	int	rawht();
+};
+
+// A generator iterates through all the ranges of a stream
+// (not just the root ranges of nestranges).
+class generator {
+	stream	s;
+	generator *child;
+  public:
+	generator()		{ child = 0; }
+	generator(stream *sp)	{ s = *sp; child = 0; }
+	range	*next();
+};
+
+extern stream	ptlist, btlist;		// page titles
+
+#define INFINITY 1000001
+
+// A queue is a distinguished kind of stream.
+// It keeps its contents in order by the serial numbers of the ranges.
+// A queue can be blocked from dequeuing something to indicate
+// that it's not worth considering the queue again on a given page.
+class queue : public stream {
+	strblk	*newguy;
+  protected:
+	int	blocked;
+	void	check(char *);
+  public:
+	queue() : blocked(0)	{ }
+	range	*enqueue(range *r);
+	range	*dequeue();
+	void	block()		{ blocked = 1; }
+	void	unblock()	{ blocked = 0; }
+	int	more()		{ return !blocked && stream::more(); }
+	int	empty()		{ return !stream::more(); }
+	int	serialno()	{ return empty() ? INFINITY : current()->serialno(); }
+};
+
+// functions in range.c
+void checkout();
+void startup(FILE *);
blob - /dev/null
blob + 14378b909cc234dbbb621dbc3fc4abe39a4ec6fc (mode 644)
--- /dev/null
+++ src/cmd/mpm/slug.cc
@@ -0,0 +1,603 @@
+#include	"misc.h"
+#include	"slug.h"
+//#include	<libc.h>
+#include	<math.h>
+
+static char	*bufptr(int);
+
+void slug::coalesce()
+{
+	(this+1)->dp = dp;	// pretty grimy, but meant to ensure
+				// that all output goes out.
+			// maybe it has to skip over PT's;
+			// some stuff is getting pushed inside PT..END
+}
+
+void slug::neutralize()
+{
+	switch (type) {
+	case PAGE:
+	case UF:
+	case BF:
+	case PARM:
+		type = NEUTRAL;
+		coalesce();
+		break;
+	default:
+		ERROR "neutralized %d (%s) with %s\n",
+			type, typename(), headstr() WARNING;
+		break;
+	}
+}
+
+void slug::dump()	// print contents of a slug
+{
+	printf("# %d %-4.4s parm %d dv %d base %d s%d f%d H%d\n#\t\t%s\n",
+		serialno(), typename(), parm, dv, base,
+		size, font, hpos, headstr());
+}
+
+char *slug::headstr()
+{
+	const int HEADLEN = 65;
+	static char buf[2*HEADLEN];
+	int j = 0;
+	char *s = bufptr(dp);
+	int n = (this+1)->dp - dp;
+	if (n >= HEADLEN)
+		n = HEADLEN;
+	for (int i = 0; i < n; i++)
+		switch (s[i]) {
+			case '\n':
+			case '\t':
+			case '\0':
+			case ' ':
+				break;
+			default:
+				buf[j++] = s[i];
+				break;
+		}
+	buf[j] = 0;
+	return buf;
+}
+
+static char *strindex(char s[], char t[])	// index of earliest t[] in s[]
+{
+	for (int i = 0; s[i] != '\0'; i++) {
+		int j, k;
+		for (j = i, k = 0; t[k]!='\0' && s[j] == t[k]; j++, k++)
+			;
+		if (k > 0 && t[k] == '\0')
+			return s+i;
+	}
+	return 0;
+}
+
+void slug::slugout(int col)
+{
+	static int numout = 0;
+	if (seen++)
+		ERROR "%s slug #%d seen %d times [%s]\n",
+			typename(), serialno(), seen, headstr() WARNING;
+	if (type == TM) {
+		char *p;
+		if ((p = strindex(bufptr(dp), "x X TM ")) != 0)
+			p += strlen("x X TM ");		// skip junk
+		else
+			ERROR "strange TM [%s]\n", headstr() FATAL;
+		fprintf(stderr, "%d\t", userpn);	// page # as prefix
+		for ( ; p < bufptr((this+1)->dp); p++)
+			putc(*p, stderr);
+	} else if (type == COORD) {
+		for (char *p = bufptr(dp); p < bufptr((this+1)->dp) && *p != '\n'; p++)
+			putc(*p, stdout);
+		printf(" # P %d X %d", userpn, hpos + col*offset);
+		return;
+	} else if (type == VBOX) {
+		if (numout++ > 0)	// BUG??? might miss something
+			printf("s%d\nf%d\n", size, font);
+		printf("H%d\n", hpos + col*offset);
+	}
+	fwrite(bufptr(dp), sizeof(char), (this+1)->dp - dp, stdout);
+}
+
+char *slug::typename()
+{
+	static char buf[50];
+	char *p = buf;		// return value
+	switch(type) {
+	case EOF:	p = "EOF"; break;
+	case VBOX:	p = "VBOX"; break;
+	case SP:	p = "SP"; break;
+	case BS:	p = "BS"; break;
+	case US:	p = "US"; break;
+	case BF:	p = "BF"; break;
+	case UF:	p = "UF"; break;
+	case PT:	p = "PT"; break;
+	case BT:	p = "BT"; break;
+	case END:	p = "END"; break;
+	case NEUTRAL:	p = "NEUT"; break;
+	case PAGE:	p = "PAGE"; break;
+	case TM:	p = "TM"; break;
+	case COORD:	p = "COORD"; break;
+	case NE:	p = "NE"; break;
+	case CMD:	p = "CMD"; break;
+	case PARM:	p = "PARM"; break;
+	default:	sprintf(buf, "weird type %d", type);
+	}
+	return p;
+}
+
+// ================================================================================
+
+// 	troff output-specific functions
+
+// ================================================================================
+
+const int	DELTABUF = 500000;	// grow the input buffer in chunks
+
+static char	*inbuf = 0;		// raw text input collects here
+static int	ninbuf = 0;		// byte count for inbuf
+static char	*inbp = 0;		// next free slot in inbuf
+int		linenum = 0;		// input line number
+
+static inline void addc(int c) { *inbp++ = c; }
+
+static void adds(char *s)
+{
+	for (char *p = s; *p; p++)
+		addc(*p);
+}
+
+static char *getutf(FILE *fp)	// get 1 utf-encoded char (might be multiple bytes)
+{
+	static char buf[100];
+	char *p = buf;
+
+	for (*p = 0; (*p++ = getc(fp)) != EOF; ) {
+		*p = 0;
+		if (mblen(buf, sizeof buf) > 0)	// found a valid character
+			break;
+	}
+	return buf;
+}
+
+static char *bufptr(int n) { return inbuf + n; }  // scope of inbuf is too local
+
+static inline int wherebuf() { return inbp - inbuf; }
+
+static char *getstr(char *p, char *temp)
+{		// copy next non-blank string from p to temp, update p
+	while (*p == ' ' || *p == '\t' || *p == '\n')
+		p++;
+	if (*p == '\0') {
+		temp[0] = 0;
+		return(NULL);
+	}
+	while (*p != ' ' && *p != '\t' && *p != '\n' && *p != '\0')
+		*temp++ = *p++;
+	*temp = '\0';
+	return(p);
+}
+
+/***************************************************************************
+   bounding box of a circular arc             Eric Grosse  24 May 84
+
+Conceptually, this routine generates a list consisting of the start,
+end, and whichever north, east, south, and west points lie on the arc.
+The bounding box is then the range of this list.
+    list = {start,end}
+    j = quadrant(start)
+    k = quadrant(end)
+    if( j==k && long way 'round )  append north,west,south,east
+    else
+      while( j != k )
+         append center+radius*[j-th of north,west,south,east unit vectors]
+         j += 1  (mod 4)
+    return( bounding box of list )
+The following code implements this, with simple optimizations.
+***********************************************************************/
+
+static int quadrant(double x, double y)
+{
+	if (     x>=0.0 && y> 0.0) return(1);
+	else if( x< 0.0 && y>=0.0) return(2);
+	else if( x<=0.0 && y< 0.0) return(3);
+	else if( x> 0.0 && y<=0.0) return(4);
+	else			   return 0;	/* shut up lint */
+}
+
+static double xmin, ymin, xmax, ymax;	// used by getDy
+
+static void arc_extreme(double x0, double y0, double x1, double y1, double xc, double yc)
+		/* start, end, center */
+{		/* assumes center isn't too far out */
+	double r;
+	int j, k;
+	printf("#start %g,%g, end %g,%g, ctr %g,%g\n", x0,y0, x1,y1, xc,yc);
+	y0 = -y0; y1 = -y1; yc = -yc;	// troff's up is eric's down
+	x0 -= xc; y0 -= yc;	/* move to center */
+	x1 -= xc; y1 -= yc;
+	xmin = (x0<x1)?x0:x1; ymin = (y0<y1)?y0:y1;
+	xmax = (x0>x1)?x0:x1; ymax = (y0>y1)?y0:y1;
+	r = sqrt(x0*x0 + y0*y0);
+	if (r > 0.0) {
+		j = quadrant(x0,y0);
+		k = quadrant(x1,y1);
+		if (j == k && y1*x0 < x1*y0) {
+			/* viewed as complex numbers, if Im(z1/z0)<0, arc is big */
+			if( xmin > -r) xmin = -r; if( ymin > -r) ymin = -r;
+			if( xmax <  r) xmax =  r; if( ymax <  r) ymax =  r;
+		} else {
+			while (j != k) {
+				switch (j) {
+				case 1: if( ymax <  r) ymax =  r; break; /* north */
+				case 2: if( xmin > -r) xmin = -r; break; /* west */
+				case 3: if( ymin > -r) ymin = -r; break; /* south */
+				case 4: if( xmax <  r) xmax =  r; break; /* east */
+				}
+				j = j%4 + 1;
+			}
+		}
+	}
+	xmin += xc; ymin += yc; ymin = -ymin;
+	xmax += xc; ymax += yc; ymax = -ymax;
+}
+
+
+static int getDy(char *p, int *dx, int *maxv)
+				// figure out where we are after a D'...'
+{
+	int x, y, x1, y1;	// for input values
+	char temp[50];
+	p++;		// get to command letter
+	switch (*p++) {
+	case 'l':	// line
+		sscanf(p, "%d %d", dx, &y);
+		return *maxv = y;
+	case 'a':	// arc
+		sscanf(p, "%d %d %d %d", &x, &y, &x1, &y1);
+		*dx = x1 - x;
+		arc_extreme(0, 0, x+x1, y+y1, x, y);	// sets [xy][max|min]
+		printf("#arc bounds x %g, %g; y %g, %g\n",
+			xmin, xmax, ymin, ymax);
+		*maxv = (int) (ymin+0.5);
+		return y + y1;
+	case '~':	// spline
+		for (*dx = *maxv = y = 0; (p=getstr(p, temp)) != NULL; ) {
+						// above getstr() gets x value
+			*dx += atoi(temp);
+			p = getstr(p, temp);	// this one gets y value
+			y += atoi(temp);
+			*maxv = max(*maxv, y);	// ok???
+			if (*p == '\n' || *p == 0)	// input is a single line;
+				break;			// don't walk off end if realloc
+		}
+		return y;
+	case 'c':	// circle, ellipse
+		sscanf(p, "%d", dx);
+		*maxv = *dx/2;		// high water mark is ht/2
+		return 0;
+	case 'e':
+		sscanf(p, "%d %d", dx, &y);
+		*maxv = y/2;		// high water mark is ht/2
+		return 0;
+	default:	// weird stuff
+		return 0;
+	}
+}
+
+static int serialnum = 0;
+
+slug eofslug()
+{
+	slug ret;
+	ret.serialnum = serialnum;
+	ret.type = EOF;
+	ret.dp = wherebuf();
+	return ret;
+}
+
+slug getslug(FILE *fp)
+{
+	if (inbuf == NULL) {
+		if ((inbuf = (char *) malloc(ninbuf = DELTABUF)) == NULL)
+			ERROR "no room for %d character input buffer\n", ninbuf FATAL;
+		inbp = inbuf;
+	}
+	if (wherebuf() > ninbuf-5000) {
+		// this is still flaky -- lines can be very long
+		int where = wherebuf();	// where we were
+		if ((inbuf = (char *) realloc(inbuf, ninbuf += DELTABUF)) == NULL)
+			ERROR "no room for %d character input buffer\n", ninbuf FATAL;
+		ERROR "grew input buffer to %d characters\n", ninbuf WARNING;
+		inbp = inbuf + where;	// same offset in new array
+	}
+	static int baseV = 0;	// first V command of preceding slug
+	static int curV = 0, curH = 0;
+	static int font = 0, size = 0;
+	static int baseadj = 0;
+	static int ncol = 1, offset = 0;	// multi-column stuff
+	char str[1000], str2[1000], buf[3000], *p;
+	int firstV = 0, firstH = 0;
+	int maxV = curV;
+	int ocurV = curV, mxv = 0, dx = 0;
+	int sawD = 0;		// > 0 if have seen D...
+	slug ret;
+	ret.serialnum = serialnum++;
+	ret.type = VBOX;	// use the same as last by default
+	ret.dv = curV - baseV;
+	ret.hpos = curH;
+	ret.base =  ret.parm = ret.parm2 = ret.seen = 0;
+	ret.font = font;
+	ret.size = size;
+	ret.dp = wherebuf();
+	ret.ncol = ncol;
+	ret.offset = offset;
+	ret.linenum = linenum;	// might be low
+
+	for (;;) {
+		int c, m, n;	// for input values
+		int sign;		// hoisted from case 'h' below
+		switch (c = getc(fp)) {
+		case EOF:
+			ret.type = EOF;
+			ret.dv = 0;
+			if (baseadj)
+				printf("# adjusted %d bases\n", baseadj);
+			printf("# %d characters, %d lines\n", wherebuf(), linenum);
+			return ret;
+		case 'V':
+			fscanf(fp, "%d", &n);
+			if (firstV++ == 0) {
+				ret.dv = n - baseV;
+				baseV = n;
+			} else {
+				sprintf(buf, "v%d", n - curV);
+				adds(buf);
+			}
+			curV = n;
+			maxV = max(maxV, curV);
+			break;
+		case 'H':		// absolute H motion
+			fscanf(fp, "%d", &n);
+			if (firstH++ == 0) {
+				ret.hpos = n;
+			} else {
+				sprintf(buf, "h%d", n - curH);
+				adds(buf);
+			}
+			curH = n;
+			break;
+		case 'h':		// relative H motion
+			addc(c);
+			sign = 1;
+			if ((c = getc(fp)) == '-') {
+				addc(c);
+				sign = -1;
+				c = getc(fp);
+			}
+			for (n = 0; isdigit(c); c = getc(fp)) {
+				addc(c);
+				n = 10 * n + c - '0';
+			}
+			curH += n * sign;
+			ungetc(c, fp);
+			break;
+		case 'x':	// device control: x ...
+			addc(c);
+			fgets(buf, (int) sizeof(buf), fp);
+			linenum++;
+			adds(buf);
+			if (buf[0] == ' ' && buf[1] == 'X') {	// x X ...
+				if (2 != sscanf(buf+2, "%s %d", str, &n))
+					n = 0;
+				if (eq(str, "SP")) {	// X SP n
+					ret.type = SP;	// paddable SPace
+					ret.dv = n;	// of height n
+				} else if (eq(str, "BS")) {
+					ret.type = BS;	// Breakable Stream
+					ret.parm = n;	// >=n VBOXES on a page
+				} else if (eq(str, "BF")) {
+					ret.type = BF;	// Breakable Float
+					ret.parm = ret.parm2 = n;
+							// n = pref center (as UF)
+				} else if (eq(str, "US")) {
+					ret.type = US;	// Unbreakable Stream
+					ret.parm = n;
+				} else if (eq(str, "UF")) {
+					ret.type = UF;	// Unbreakable Float
+					ret.parm = ret.parm2 = n;
+							// n = preferred center
+							// to select several,
+							// use several UF lines
+				} else if (eq(str, "PT")) {
+					ret.type = PT;	// Page Title
+					ret.parm = n;
+				} else if (eq(str, "BT")) {
+					ret.type = BT;	// Bottom Title
+					ret.parm = n;
+				} else if (eq(str, "END")) {
+					ret.type = END;
+					ret.parm = n;
+				} else if (eq(str, "TM")) {
+					ret.type = TM;	// Terminal Message
+					ret.dv = 0;
+				} else if (eq(str, "COORD")) {
+					ret.type = COORD;// page COORDinates
+					ret.dv = 0;
+				} else if (eq(str, "NE")) {
+					ret.type = NE;	// NEed to break page
+					ret.dv = n;	// if <n units left
+				} else if (eq(str, "MC")) {
+					ret.type = MC;	// Multiple Columns
+					sscanf(buf+2, "%s %d %d",
+						str, &ncol, &offset);
+					ret.ncol = ncol;
+					ret.offset = offset;
+				} else if (eq(str, "CMD")) {
+					ret.type = CMD;	// CoMmaNd
+					sscanf(buf+2, "%s %s", str2, str);
+					if (eq(str, "FC"))	// Freeze 2-Col
+						ret.parm = FC;
+					else if (eq(str, "FL"))	// FLush
+						ret.parm = FL;
+					else if (eq(str, "BP"))	// Break Page
+						ret.parm = BP;
+					else ERROR "unknown command %s\n",
+						str WARNING;
+				} else if (eq(str, "PARM")) {
+					ret.type = PARM;// PARaMeter
+					sscanf(buf+2, "%s %s %d", str2, str, &ret.parm2);
+					if (eq(str, "NP"))	// New Page
+						ret.parm = NP;
+					else if (eq(str, "FO"))	// FOoter
+						ret.parm = FO;
+					else if (eq(str, "PL")) // Page Length
+						ret.parm = PL;
+					else if (eq(str, "MF")) // MinFull
+						ret.parm = MF;
+					else if (eq(str, "CT")) // ColTol
+						ret.parm = CT;
+					else if (eq(str, "WARN")) //WARNings?
+						ret.parm = WARN;
+					else if (eq(str, "DBG"))// DeBuG
+						ret.parm = DBG;
+					else ERROR "unknown parameter %s\n",
+						str WARNING;
+				} else
+					break;		// out of switch
+				if (firstV > 0)
+					ERROR "weird x X %s in mid-VBOX\n",
+						str WARNING;
+				return ret;
+			}
+			break;
+		case 'n':	// end of line
+			fscanf(fp, "%d %d", &n, &m);
+			ret.ht = n;
+			ret.base = m;
+			getc(fp);	// newline
+			linenum++;
+			sprintf(buf, "n%d %d\n", ret.ht, ret.base);
+			adds(buf);
+			if (!firstV++)
+				baseV = curV;
+			// older incarnations of this program used ret.base
+			// in complicated and unreliable ways;
+			// example:  if ret.ht + ret.base < ret.dv, ret.base = 0
+			// this was meant to avoid double-counting the space
+			// around displayed equations; it didn't work
+			// Now, we believe ret.base = 0, otherwise we give it
+			// a value we have computed.
+			if (ret.base == 0 && sawD == 0)
+				return ret;	// don't fiddle 0-bases
+			if (ret.base != maxV - baseV) {
+				ret.base = maxV - baseV;
+				baseadj++;
+			}
+			if (ret.type != VBOX)
+				ERROR "%s slug (type %d) has base = %d\n",
+					ret.typename(), ret.type, ret.base WARNING;
+			return ret;
+		case 'p':	// new page
+			fscanf(fp, "%d", &n);
+			ret.type = PAGE;
+			curV = baseV = ret.dv = 0;
+			ret.parm = n;	// just in case someone needs it
+			return ret;
+		case 's':	// size change snnn
+			fscanf(fp, "%d", &size);
+			sprintf(buf, "s%d\n", size);
+			adds(buf);
+			break;
+		case 'f':	// font fnnn
+			fscanf(fp, "%d", &font);
+			sprintf(buf, "f%d\n", font);
+			adds(buf);
+			break;
+		case '\n':
+			linenum++;
+			/* fall through */
+		case ' ':
+			addc(c);
+			break;
+		case '0': case '1': case '2': case '3': case '4':
+		case '5': case '6': case '7': case '8': case '9':
+			// two motion digits plus a character
+			addc(c);
+			n = c - '0';
+			addc(c = getc(fp));
+			curH += 10 * n + c - '0';
+			adds(getutf(fp));
+			if (!firstV++)
+				baseV = curV;
+			break;
+		case 'c':	// single ascii character
+			addc(c);
+			adds(getutf(fp));
+			if (!firstV++)
+				baseV = curV;
+			break;
+		case 'C':	// Cxyz\n
+		case 'N':	// Nnnn\n
+			addc(c);
+			while ((c = getc(fp)) != ' ' && c != '\n')
+				addc(c);
+			addc(c);
+			if (!firstV++)
+				baseV = curV;
+			linenum++;
+			break;
+		case 'D':	// draw function: D.*\n
+			sawD++;
+			p = bufptr(wherebuf());	// where does the D start
+			addc(c);
+			while ((c = getc(fp)) != '\n')
+				addc(c);
+			addc(c);
+			if (!firstV++)
+				baseV = curV;
+			ocurV = curV, mxv = 0, dx = 0;
+			curV += getDy(p, &dx, &mxv);	// figure out how big it is
+			maxV = max(max(maxV, curV), ocurV+mxv);
+			curH += dx;
+			linenum++;
+			break;
+		case 'v':	// relative vertical vnnn
+			addc(c);
+			if (!firstV++)
+				baseV = curV;
+			sign = 1;
+			if ((c = getc(fp)) == '-') {
+				addc(c);
+				sign = -1;
+				c = getc(fp);
+			}
+			for (n = 0; isdigit(c); c = getc(fp)) {
+				addc(c);
+				n = 10 * n + c - '0';
+			}
+			ungetc(c, fp);
+			curV += n * sign;
+			maxV = max(maxV, curV);
+			addc('\n');
+			break;
+		case 'w':	// word space
+			addc(c);
+			break;
+		case '#':	// comment
+			addc(c);
+			while ((c = getc(fp)) != '\n')
+				addc(c);
+			addc('\n');
+			linenum++;
+			break;
+		default:
+			ERROR "unknown input character %o %c (%50.50s)\n",
+				c, c, bufptr(wherebuf()-50) WARNING;
+			abort();
+			break;
+		}
+	}
+}
blob - /dev/null
blob + 9dfd3b2a1b4f1cd07277ca9934312f01e54f2b3e (mode 644)
--- /dev/null
+++ src/cmd/mpm/slug.h
@@ -0,0 +1,74 @@
+enum slugtypes {
+	NONE,		// can't happen
+	VBOX,		// Vertical Box -- printable stuff
+	SP,		// paddable SPace
+	BS,		// start Breakable Stream
+	US,		// start Unbreakable Stream
+	BF,		// start Breakable Float
+	UF,		// start Unbreakable Float
+	PT,		// start Page Top material (header)
+	BT,		// start page BoTtom material (footer)
+	END,		// ENDs of groups
+	NEUTRAL,	// NEUTRALized slugs can do no harm (cf. CIA)
+	PAGE,		// beginning of PAGE in troff input
+	TM,		// Terminal Message to appear during output
+	COORD,		// output page COORDinates
+	NE,		// NEed command
+	MC,		// Multiple-Column command
+	CMD,		// misc CoMmanDs:  FC, FL, BP
+	PARM,		// misc PARaMeters:  NP, FO
+	LASTTYPE	// can't happen either
+};
+
+enum cmdtypes {
+	FC,	// Freeze 2-Column material
+	FL,	// FLush all floats before reading more stream
+	BP	// Break Page
+};
+
+enum parmtypes {
+	NP,	// distance of top margin from page top (New Page)
+	FO,	// distance of bottom margin from page top (FOoter)
+	PL,	// distance of physical page bottom from page top (Page Length)
+	MF,	// minimum fullness required for padding
+	CT,	// tolerance for division into two columns
+	WARN,	// warnings to stderr?	
+	DBG	// debugging flag
+};
+
+class slug {
+	int	serialnum;
+	int	dp;		// offset of data for this slug in inbuf
+	int	linenum;	// input line number (approx) for this slug
+	short	font;		// font in effect at slug beginning
+	short	size;		// size in effect at slug beginning
+	short	seen;		// 0 until output
+	short	ncol;		// number of columns (1 or 2)
+	short	offset;		// horizontal offset for 2 columns
+  public:
+	short	type;		// VBOX, PP, etc.
+	short	parm;		// parameter
+	short	base;		// "depth" of this slug (from n command)
+	int	hpos;		// abs horizontal position
+	int	dv;		// height of this slug above its input Vpos
+	union {
+		int	ht;	// "height" of this slug (from n command)
+		int	parm2;	// second parameter, since only VBOXes have ht
+	};
+	friend	slug getslug(FILE *);
+	friend	void checkout();
+	friend	slug eofslug();
+	void	coalesce();	// with next slug in array slugs[]
+	void	neutralize();	// render this one a no-op
+	void	dump();		// dump its contents for debugging
+	char	*headstr();	// string value of text
+	void	slugout(int);	// add the slug to the output
+	char	*typename();	// printable slug type
+	int	serialno()	{ return serialnum; }
+	int	numcol()	{ return ncol; }
+	int	lineno()	{ return linenum; }
+};
+
+// functions in slug.c
+slug	eofslug();
+slug	getslug(FILE *);
blob - /dev/null
blob + 7ce0146614e50263068ba585f10e1968ce4c2e3d (mode 644)
--- /dev/null
+++ src/cmd/mpm/tmac.pm
@@ -0,0 +1,961 @@
+.\" 10/22/92 activate next line before installing
+.pi /$objtype/bin/aux/pm
+.
+.		\" IZ - initialization
+.de IZ
+.fp 1 R			\" force a font out into prefix
+.nr PS 10		\" point size
+.nr VS 12		\" line spacing
+.ps \\n(PS
+.ie \\n(VS>=41 .vs \\n(VSu
+.el .vs \\n(VSp
+.nr LL 6i		\" line length
+.ll \\n(LLu
+.nr LT \\n(.l		\" title length
+.lt \\n(LTu
+.if !\\n(HM .nr HM 1i   \" top of page
+.if !\\n(FM .nr FM 1i	\" footer margin
+.if !\\n(FO .nr FO \\n(.p-\\n(FM	\" bottom of page
+.			\" to set text ht to N, set FO to N + \n(HM.  default is 10i
+.pl 32767u		\" safety first: big pages for pm
+.if !\\n(PO .nr PO \\n(.ou	\" page offset
+.nr PI 5n		\" .PP paragraph indent
+.nr QI 5n		\" .QS indent
+.nr DI 5n		\" .DS indent
+.nr PD 0.3v		\" paragraph vertical separation
+.nr TS 0.5v		\" space around tables
+.nr Kf 0.5v		\" space around .KF/.KE
+.nr Ks 0.5v		\" space around .KS/.KE
+.
+.nr P1 .4i		\" indent for .P1/.P2
+.nr dP 1		\" delta point size for programs in .P1/.P2
+.nr dV 2p		\" delta vertical for programs
+.nr dT 8		\" delta tab stop for programs
+.nr DV .5v		\" space before start of program
+.nr IP 0		\" ?
+.nr IR 0		\" ?
+.nr I1 \\n(PIu
+.ev 1
+.if !\\n(FL .nr FL \\n(LLu	\" footnote length
+.ll \\n(FLu
+.ps 8			\" text size & leading in footnote
+.vs 10p
+.ev
+.if \\*(CH .ds CH "\(hy \\\\n(PN \(hy
+.ds # #\\\\n(.c \\\\n(.F
+.
+.
+.ME	\" initialize date strings
+.rm ME
+.	\"  accents:  \*'e \*`e \*:u \*^e \*~n \*va \*,c
+.ds ' \h'\w'e'u*4/10'\z\(aa\h'-\w'e'u*4/10'
+.ds ` \h'\w'e'u*4/10'\z\(ga\h'-\w'e'u*4/10'
+.ds : \\v'-0.6m'\\h'(1u-(\\\\n(.fu%2u))*0.13m+0.00m'\\z.\\h'0.2m'\\z.\\h'-((1u-(\\\\n(.fu%2u))*0.13m+0.20m)'\\v'0.6m'
+.ds ^ \\\\k:\\h'-\\\\n(.fu+1u/2u*2u+\\\\n(.fu-1u*0.13m+0.06m'\\z^\\h'|\\\\n:u'
+.ds ~ \\\\k:\\h'-\\\\n(.fu+1u/2u*2u+\\\\n(.fu-1u*0.13m+0.06m'\\z~\\h'|\\\\n:u'
+.ds v \\\\k:\\\\h'+\\\\w'e'u/4u'\\\\v'-0.6m'\\\\s6v\\\\s0\\\\v'0.6m'\\\\h'|\\\\n:u'
+.ds , \\\\k:\\\\h'\\\\w'c'u*0.4u'\\\\z,\\\\h'|\\\\n:u'
+..
+.
+.
+.		\" SP - generate paddable space
+.de SP
+.br
+.nr X 1v
+.if \\n(.$ .nr X \\$1v
+.ie '\\$2'exactly' \{\
+\v'\\nXu'\ \h'-\w'\ 'u'\c
+.sp \\$1\}
+.el .X "SP \\nX \\$2"
+..
+.		\" NE - need space on this page
+.de NE
+.nr X 1v
+.if \\n(.$ .nr X \\$1v
+.X "NE \\nX \\$2"
+..
+.		\" BP, FL, FC - begin page, flush figures, flush column
+.de BP
+.br
+.X CMD BP
+..
+.de FL
+.br
+.X CMD FL
+..
+.de FC
+.br
+.X CMD FC
+..
+.		\" X - generate an x X ... command in the output
+.de X
+....ie '\\n(.z'' \\!x X \\$1 \\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8 \\$9
+....el \\!.X "\\$1 \\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8 \\$9
+...
+.if !'\\n(.z'' .if \\n(.$=1 \\!.X "\\$1
+.if !'\\n(.z'' .if \\n(.$=2 \\!.X "\\$1 \\$2
+.if !'\\n(.z'' .if \\n(.$=3 \\!.X "\\$1 \\$2 \\$3
+.if !'\\n(.z'' .if \\n(.$>3 \\!.X "\\$1 \\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8 \\$9
+.if '\\n(.z'' .if \\n(.$=1 \\!x X \\$1 \\*#
+.if '\\n(.z'' .if \\n(.$=2 \\!x X \\$1 \\$2 \\*#
+.if '\\n(.z'' .if \\n(.$=3 \\!x X \\$1 \\$2 \\$3 \\*#
+.if '\\n(.z'' .if \\n(.$=4 \\!x X \\$1 \\$2 \\$3 \\$4 \\*#
+.if '\\n(.z'' .if \\n(.$>4 \\!x X \\$1 \\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8 \\$9 \\*#
+..
+.		\" DA - force date
+.de DA
+.if \\n(.$ .ds DY \\$1 \\$2 \\$3 \\$4
+.ds CF \\*(DY
+..
+.		\" ND - set new or no date
+.de ND
+.ds DY \\$1 \\$2 \\$3 \\$4
+.rm CF
+..
+.de ME		\" ME - set month strings
+.if \\n(mo-0 .ds MO January
+.if \\n(mo-1 .ds MO February
+.if \\n(mo-2 .ds MO March
+.if \\n(mo-3 .ds MO April
+.if \\n(mo-4 .ds MO May
+.if \\n(mo-5 .ds MO June
+.if \\n(mo-6 .ds MO July
+.if \\n(mo-7 .ds MO August
+.if \\n(mo-8 .ds MO September
+.if \\n(mo-9 .ds MO October
+.if \\n(mo-10 .ds MO November
+.if \\n(mo-11 .ds MO December
+.if \\n(dw-0 .ds DW Sunday
+.if \\n(dw-1 .ds DW Monday
+.if \\n(dw-2 .ds DW Tuesday
+.if \\n(dw-3 .ds DW Wednesday
+.if \\n(dw-4 .ds DW Thursday
+.if \\n(dw-5 .ds DW Friday
+.if \\n(dw-6 .ds DW Saturday
+.if "\\*(DY"" .ds DY \\*(MO \\n(dy, 19\\n(yr
+..
+.		\" FP - font position for a family
+.de FP
+.if '\\$1'palatino'\{\
+.	fp 1 PA
+.	fp 2 PI
+.	fp 3 PB
+.	fp 4 PX\}
+.if '\\$1'century'\{\
+.	ie '\\*(.T'202'\{\
+.		fp 1 NR Centsb
+.		fp 2 NI CentI
+.		fp 3 NB CentB
+.		fp 4 NX CentBI\}
+.	el \{\
+.		fp 1 NR
+.		fp 2 NI
+.		fp 3 NB
+.		fp 4 NX\}\}
+.if '\\$1'helvetica'\{\
+.	fp 1 H
+.	fp 2 HI
+.	fp 3 HB
+.	fp 4 HX\}
+.if '\\$1'bembo'\{\
+.	ie '\\*(.T'202'\{\
+.		fp 1 B1 Bembo
+.		fp 2 B2 BemboI
+.		fp 3 B3 BemboB
+.		fp 4 B4 BemboBI\}
+.	el \{\
+.		fp 1 B1
+.		fp 2 B2
+.		fp 3 B3
+.		fp 4 B4\}\}
+.if '\\$1'optima'\{\
+.	fp 1 R Optima
+.	fp 2 I OptimaI
+.	fp 3 B OptimaB
+.	fp 4 BI OptimaBI\}
+.if '\\$1'souvenir'\{\
+.	fp 1 R Souvenir
+.	fp 2 I SouvenirI
+.	fp 3 B SouvenirB
+.	fp 4 BI SouvenirBI\}
+.if '\\$1'melior'\{\
+.	fp 1 R Melior
+.	fp 2 I MeliorI
+.	fp 3 B MeliorB
+.	fp 4 BI MeliorBI\}
+.if '\\$1'times'\{\
+.	fp 1 R
+.	fp 2 I
+.	fp 3 B
+.	fp 4 BI\}
+..
+.		\" TL - title
+.de TL
+.br
+.if !\\n(1T .BG
+....hy 0
+.ft 3
+.ps \\n(PS+2p
+.vs \\n(VS+2p
+.ll \\n(LLu
+.ce 100		\" turned off in .RT
+.sp .5i
+..
+.		\" AU - remember author(s)
+.de AU
+.ft 1
+.ps \\n(PS
+.ie \\n(VS>=41 .vs \\n(VSu
+.el .vs \\n(VSp
+.SP .5
+..
+.		\" AI - author's institution
+.de AI
+.SP .25
+.ft 2
+..
+.		\" AB - begin abstract
+.de AB
+.nr AB 1	  \" we're in abstract
+.if !\\n(1T .BG
+.ft 1
+.ps \\n(PS
+.vs \\n(VSp
+.ce
+.in +\\n(.lu/12u
+.ll -\\n(.lu/12u
+.SP 1
+.ie \\n(.$ \\$1
+.el ABSTRACT
+.SP .75 
+.RT
+..
+.		\" AE - end of abstract
+.de AE
+.br
+.nr AB 0
+.in 0
+.ll \\n(LLu
+.ps \\n(PS
+.ie \\n(VS>=41 .vs \\n(VSu
+.el .vs \\n(VSp
+.SP
+..
+.		\" 2C - 2 columns
+.de 2C
+.MC 2
+..
+.		\" 1C - 1 column
+.de 1C
+.MC 1
+..
+.		\" MC - multiple columns
+.de MC
+.br
+.if \\n(1T .RT
+.if \\n(1T .NP
+.if !\\n(OL .nr OL \\n(LL
+.if \\n(CW=0 .nr CW \\n(LL*7/15
+.if \\n(GW=0 .nr GW \\n(LL-(2*\\n(CW)
+.nr x \\n(CW+\\n(GW
+.if "\\$1"" .MC 2
+.if \\$1=1 \{\
+.	X MC 1 0
+.	nr LL \\n(OLu\}
+.if \\$1=2 \{\
+.	X MC 2 \\nx
+.	nr LL \\n(CWu\}
+.ll \\n(LLu
+.if \\$1>2 .tm -mpm can't handle more than two columns
+.if \\n(1T .RT
+..
+.		\" TS - table start, TE - table end;  also TC, TQ, TH
+.de TS
+.br
+.if !\\n(1T .RT
+.SP \\n(TSu TS
+.X "US TS
+.if \\$1H .TQ
+.nr IX 1
+..
+.de TC
+.nr TZ \\n(.lu
+.if \\n(.$ .nr TZ \\$1n
+.ta \\n(TZuR
+..
+.de TD
+.LP
+.nr TZ 0
+..
+.de TQ
+.di TT
+.nr IT 1
+..
+.de TH
+.if \\n(.d>0.5v \{\
+.	nr T. 0
+.	T# 0\}
+.di
+.nr TQ \\n(.i
+.nr HT 1
+.in 0
+.mk #a
+.mk #b
+.mk #c
+.mk #d
+.mk #e
+.mk #f
+.TT
+.in \\n(TQu
+.mk #T
+..
+.		\" TE - table end
+.de TE
+.nr IX 0
+.if \\n(IT .if !\\n(HT \{\
+.	di
+.	nr EF \\n(.u
+.	nf
+.	TT
+.	if \\n(EF .fi\}
+.nr IT 0
+.nr HT 0
+.rm a+ b+ c+ d+ e+ f+ g+ h+ i+ j+ k+ l+ n+ m+
+.rr 32 33 34 35 36 37 38 40 79 80 81 82
+.rr a| b| c| d| e| f| g| h| i| j| k| l| m|
+.rr a- b- c- d- e- f- g- h- i- j- k- l- m-
+.X "END US TE
+.SP \\n(TSu TE
+.bp
+..
+.		\" EQ - equation, breakout and display
+.de EQ
+.nr EF \\n(.u
+.rm EE
+.nr LE 1	\" 1 is center
+.ds EL \\$1
+.if "\\$1"L" \{\
+.	ds EL \\$2
+.	nr LE 0\}
+.if "\\$1"C" .ds EL \\$2
+.if "\\$1"R" \{\
+.	ds EL \\$2 \" 2 is right adjust
+.	nr LE 2\}
+.if "\\$1"I" \{\
+.	nr LE 0
+.	if "\\$3"" .ds EE \\h'|10n'
+.	el .ds EE \\h'\\$3'
+.	ds EL \\$2\}
+.if \\n(YE .nf
+.di EZ
+..
+.		\" EN - end of equation
+.de EN 
+.br
+.di
+.rm EZ
+.nr ZN \\n(dn
+.if \\n(ZN .if !\\n(YE .LP
+.if !\\n(ZN .if !"\\*(EL"" .nr ZN 1
+.if \\n(ZN \{\
+.	SP .5v EQ
+.	X "US EQ"\}
+'pc
+.if \\n(BD .nr LE 0 \" don't center if block display or mark/lineup
+.if \\n(MK \{\
+.	if \\n(LE=1 .ds EE \\h'|10n'
+.	nr LE 0\}
+'lt \\n(.lu
+.if !\\n(EP .if \\n(ZN \{\
+.	if \\n(LE=1 .tl \(ts\(ts\\*(10\(ts\\*(EL\(ts
+.	if \\n(LE=2 .tl \(ts\(ts\(ts\\*(10\\*(EL\(ts
+.	if !\\n(LE \{\
+.		if !\\n(BD .tl \(ts\\*(EE\\*(10\(ts\(ts\\*(EL\(ts
+.		if \\n(BD .if \\n(BD<\\w\(ts\\*(10\(ts .nr BD \\w\(ts\\*(10\(ts
+.		if \\n(BD \!\\*(10\\t\\*(EL\}\}
+.if \\n(EP .if \\n(ZN \{\
+.	if \\n(LE=1 .tl \(ts\\*(EL\(ts\\*(10\(ts\(ts
+.	if \\n(LE=2 .tl \(ts\\*(EL\(ts\(ts\\*(10\(ts
+.	if !\\n(LE \{\
+.		if !\\n(BD .tl \(ts\\*(EL\\*(EE\\*(10\(ts\(ts\(ts
+.		if \\n(BD .if \\n(BD<\\w\(ts\\*(10\(ts .nr BD \\w\(ts\\*(10\(ts
+.		if \\n(BD \!\\h'-\\\\n(.iu'\\*(EL\\h'|0'\\*(10\}\}
+'lt \\n(LLu
+'pc %
+.if \\n(YE .if \\n(EF .fi
+.if \\n(ZN .X "END US EQ"
+.if \\n(ZN .SP .5v EN
+.if \\n(ZN .bp
+..
+.		\" PS - start picture
+.de PS			\" $1 is height, $2 is width, in inches
+.br
+.nr X 0.35v
+.if \\$1>0 .X "SP \\nX PS"
+.ie \\$1>0 .nr $1 \\$1
+.el .nr $1 0
+.X "US PS \\$1
+.in (\\n(.lu-\\$2)/2u
+..
+.		\" PE - end of picture
+.de PE
+.in
+.X "END US PE
+.nr X .65v
+.if \\n($1>0 .X "SP \\nX PE"
+.bp
+..
+.de IS	\" for -mpm only
+.KS
+..
+.de IE
+.KE
+.bp
+..
+.		\" NP - new page
+.de NP
+.ev 2
+.bp
+.if \\n(KF=0 \{\
+.	nr PX \\n(.s
+.	nr PF \\n(.f
+.	nr PV \\n(.v
+.	lt \\n(LTu
+.	ps \\n(PS
+.	vs \\n(PS+2
+.	ft 1
+.	if \\n(PO .po \\n(POu	\" why isn't this reset???
+.	PT \\$1
+.	bp
+.	rs
+.	BT
+.	bp
+.	nr %# +1
+.	ps \\n(PX
+.	vs \\n(PVu
+.	ft \\n(PF \}
+.ev
+..
+.
+.ds %e .tl '\\*(LH'\\*(CH'\\*(RH'
+.ds %o .tl '\\*(LH'\\*(CH'\\*(RH'
+.ds %E .tl '\\*(LF'\\*(CF'\\*(RF'
+.ds %O .tl '\\*(LF'\\*(CF'\\*(RF'
+.
+.		\" PT - page title
+.de PT
+.nr PN \\n(%#
+.X "PT \\n(%#
+.sp \\n(HMu/2u
+.if \\n(OL .lt \\n(OLu		\" why isn't this reset???
+.if \\n(BT>0 .if \\n(%#%2 \\*(%o
+.if \\n(BT>0 .if !\\n(%#%2 \\*(%e
+.if \\n(BT=0 .tl '\0'''		\" put out something or spacing is curdled
+.X "END PT \\n(%#
+..
+.		\" BT - bottom title
+.de BT
+.X "BT \\n(%#
+.sp |\\n(FMu/2u+\\n(FOu-1v
+.if \\n(%#%2 \\*(%O
+.if !\\n(%#%2 \\*(%E
+.nr BT \\n(BT+1
+.X "END BT \\n(%#
+..
+.		\" KS - non-floating keep
+.de KS
+.br
+.if "\\n(.z"" .NP  \" defends poorly against including ht of page stuff in diversion for .B1
+.X "US KS 0
+.nr KS +1
+.SP \\n(Ksu
+..
+.		\" KF - floating keep
+.de KF
+.ev 1
+.br
+.if \\n(KS>0 .tm KF won't work inside KS, line \\n(.c, file \\n(.F
+.if \\n(KF>0 .tm KF won't work inside KF, line \\n(.c, file \\n(.F
+.nr KF 1
+.nr 10 0
+.	if !'\\$1'' .nr 10 \\$1u
+.	if '\\$1'bottom' .nr 10 \\n(FOu-1u
+.	if '\\$1'top' .nr 10 \\n(HM
+.	if \\n(10 .X "UF \\n(10 KF"
+.	if !\\n(10 .X "UF \\n(HM KF"
+.	nr X \\n(FOu-2u
+.	if \\n(10 .X "UF \\n(10 KF"
+.	if !\\n(10 .X "UF \\nX KF"
+.nr SJ \\n(.u
+.ps \\n(PS
+.if \\n(VS>40 .vs \\n(VSu
+.if \\n(VS<=39 .vs \\n(VSp
+.ll \\n(LLu
+.lt \\n(LTu
+.SP \\n(Kfu
+..
+.		\" KE - end of KS/KF
+.de KE
+.bp
+.ie \\n(KS>0 \{\
+.	SP \\n(Ksu
+.	X "END US KS
+.	nr KS -1 \}
+.el .ie \\n(KF>0 \{\
+.	SP \\n(Kfu
+.	nr KF 0
+.	X "END UF KF"
+.	if \\n(SJ .fi
+.	ev \}
+.el .tm .KE without preceding .KS or .KF, line \\n(.c, file \\n(.F
+..
+.
+.		\" DS - display. .DS C center; L left-adjust; I indent (default)
+.de DS		\"  $2 = amount of indent
+.KS
+.nf
+.\\$1D \\$2 \\$1
+.ft 1
+.if !\\n(IF \{\
+.	ps \\n(PS
+.	if \\n(VS>40 .vs \\n(VSu
+.	if \\n(VS<=39 .vs \\n(VSp\}
+..
+.de D
+.ID \\$1
+..
+.de CD
+.XD
+.ce 1000
+..
+.de ID
+.XD
+.if \\n(.$=0 .in +\\n(DIu
+.if \\n(.$=1 .if "\\$1"I" .in +\\n(DIu
+.if \\n(.$=1 .if !"\\$1"I" .in +\\$1n
+.if \\n(.$>1 .in +\\$2n
+.....in +0.5i
+.....if \\n(.$ .if !"\\$1"I" .if !"\\$1"" .in \\n(DIu
+.....if \\n(.$ .if !"\\$1"I" .if !"\\$1"" .in +\\$1n
+..
+.de LD
+.XD
+..
+.de XD
+.nf
+.nr OI \\n(.i
+.SP \\n(DVu
+..
+.		\" BD - block display: save everything, then center it.
+.de BD
+.XD
+.nr BD 1
+.nf
+.in \\n(OIu
+.di DD
+..
+.		\" DE - display end
+.de DE
+.ce 0
+.if \\n(BD>0 .XF
+.nr BD 0
+.in \\n(OIu
+.SP \\n(DVu
+.KE
+.fi
+..
+.		\" XF - finish a block display to be recentered.
+.de XF
+.di
+.if \\n(dl>\\n(BD .nr BD \\n(dl
+.if \\n(BD<\\n(.l .in (\\n(.lu-\\n(BDu)/2u
+.nr EI \\n(.l-\\n(.i
+.ta \\n(EIuR
+.nf
+.DD
+.in \\n(OIu
+..
+.
+.
+.		\" SH - (unnumbered) section heading
+.de SH
+.RT
+.nr X 1v
+.nr Y 3v
+.if \\n(1T .NP
+.if \\n(1T .X "NE \\nY SH"	\" should these be reversed, change Y to 4v
+.if \\n(1T .X "SP \\nX SH
+.ft 3
+..
+.		\" NH - numbered heading
+.de NH
+.RT
+.nr X 1v
+.nr Y 3v
+.if \\n(1T .NP
+.if \\n(1T .X "NE \\nY NH"	\" should these be reversed, change Y to 4v
+.if \\n(1T .X "SP \\nX NH
+.ft 3
+.nr NS \\$1
+.if !\\n(.$ .nr NS 1
+.if !\\n(NS .nr NS 1
+.nr H\\n(NS +1
+.if !\\n(NS-4 .nr H5 0
+.if !\\n(NS-3 .nr H4 0
+.if !\\n(NS-2 .nr H3 0
+.if !\\n(NS-1 .nr H2 0
+.if !\\$1 .if \\n(.$ .nr H1 1
+.ds SN \\n(H1.
+.if \\n(NS-1 .as SN \\n(H2.
+.if \\n(NS-2 .as SN \\n(H3.
+.if \\n(NS-3 .as SN \\n(H4.
+.if \\n(NS-4 .as SN \\n(H5.
+\\*(SN
+..
+.		\" RT - reset at beginning of each PP, LP, etc.
+.de RT
+.if !\\n(AB .if !\\n(1T .BG
+.ce 0
+.if !\\n(AB .if !\\n(KF .if !\\n(IF .if !\\n(IX .if !\\n(BE .di
+.if \\n(QP \{\
+.	ll +\\n(QIu
+.	in -\\n(QIu
+.	nr QP -1\}
+.if !\\n(AB \{\
+.	ll \\n(LLu\}
+.if !\\n(IF .if !\\n(AB \{\
+.	ps \\n(PS
+.	ie \\n(VS>=41 .vs \\n(VSu
+.	el .vs \\n(VSp\}
+.ie \\n(IP \{\
+.	in \\n(I\\n(IRu
+.	nr IP -1\}
+.el .if !\\n(IR \{\
+.	nr I1 \\n(PIu
+.	nr I2 0
+.	nr I3 0
+.	nr I4 0
+.	nr I5 0\}
+.if !\\n(AB .ft 1
+.ta 5n 10n 15n 20n 25n 30n 35n 40n 45n 50n 55n 60n 65n 70n 75n 80n
+.fi
+..
+.		\" BG - begin, execute at first TL, AB, NH, SH, PP, etc.
+.de BG		\"	IZ has been called, so registers have some value
+.br
+.if \\n(CW>0 .if \\n(LL=0 .nr LL \\n(CW+\\n(CW+\\n(GW
+.ll \\n(LLu
+.lt \\n(LLu
+.po \\n(POu
+.nr YE 1		\" ok to cause break in .EQ (earlier ones won't)
+.ev 0
+.hy 14
+.ev
+.ev 1
+.hy 14
+.ev
+.ev 2
+.hy 14
+.ev
+.nr 1T 1
+.X "PARM NP \\n(HM
+.X "PARM FO \\n(FO
+.if !\\n(%# .nr %# 1
+..
+.		\" PP - paragraph
+.de PP
+.RT
+.if \\n(1T .NP
+.if \\n(1T .X "SP \\n(PD PP"
+.if \\n(1T .X "BS 2 PP"
+.ti +\\n(PIu
+..
+.		\" LP - left aligned paragraph
+.de LP
+.RT
+.if \\n(1T .NP
+.if \\n(1T .X "SP \\n(PD LP"
+.if \\n(1T .X "BS 2 LP"
+..
+.		\" IP - indented paragraph
+.de IP
+.RT
+.if !\\n(IP .nr IP +1
+.if \\n(1T .NP
+.if \\n(1T .X "SP \\n(PD PP"
+.if \\n(1T .X "BS 2 IP"
+.nr IU \\n(IR+1
+.if \\n(.$>1 .nr I\\n(IU \\$2n+\\n(I\\n(IRu
+.if \\n(I\\n(IU=0 .nr I\\n(IU \\n(PIu+\\n(I\\n(IRu
+.in \\n(I\\n(IUu
+.nr TY \\n(TZ-\\n(.i
+.nr JQ \\n(I\\n(IU-\\n(I\\n(IR
+.ta \\n(JQu \\n(TYuR
+.if \\n(.$ \{\
+.ti \\n(I\\n(IRu
+\&\\$1\t\c\}
+..
+.		\" QP - quoted paragraph (within IP)
+.de QP
+.RT
+.if \\n(1T .NP
+.if \\n(1T .X "SP \\n(PD QP"
+.if \\n(1T .X "BS 2 QP"
+.nr QP 1
+.in +\\n(QIu
+.ll -\\n(QIu
+.ti \\n(.iu
+..
+.		\" RS - prepare for double indenting
+.de RS
+.nr IS \\n(IP
+.RT
+.nr IP \\n(IS
+.nr IU \\n(IR
+.nr IR +1
+.if !\\n(I\\n(IR .nr I\\n(IR \\n(I\\n(IU+\\n(PIu
+.in \\n(I\\n(IRu
+.nr TY \\n(TZ-\\n(.i
+.ta \\n(TYuR
+..
+.		\" RE - retreat to the left
+.de RE
+.nr IS \\n(IP
+.RT
+.nr IP \\n(IS
+.if \\n(IR>0 .nr IR -1
+.in \\n(I\\n(IRu
+..
+.		\" B - bold font
+.de B
+.nr PQ \\n(.f
+.ft 3
+.if \\n(.$ \&\\$1\\f\\n(PQ\\$2
+..
+.		\" BI - bold italic
+.de BI
+.nr PQ \\n(.f
+.ft 4
+.if \\n(.$ \&\\$1\\f\\n(PQ\\$2
+..
+.		\" R - Roman font
+.de R
+.nr PQ \\n(.f
+.ft 1
+.if \\n(.$ \&\\$1\f\\n(PQ\\$2
+..
+.		\" I - italic font
+.de I
+.nr PQ \\n(.f
+.ft 2
+.if \\n(.$ \&\\$1\^\f\\n(PQ\\$2
+..
+.		\" CW - constant width font from -ms
+.de CW
+.nr PQ \\n(.f
+.if \\n(.$=0 .ft CW
+.if \\n(.$>0 \%\&\\$3\f(CW\\$1\\f\\n(PQ\\$2
+..
+.de IT		\" ditto to italicize argument
+.nr Sf \\n(.f
+\%\&\\$3\f2\\$1\f\\n(Sf\&\\$2
+..
+.		\" TA - tabs set in ens or chars
+.de TA
+.ta \\$1n \\$2n \\$3n \\$4n \\$5n \\$6n \\$7n \\$8n \\$9n
+..
+.		\" SM - make smaller size
+.de SM
+.ie \\n(.$ \&\\$3\s-2\\$1\s0\\$2
+.el .ps -2
+..
+.		\" LG - make larger size
+.de LG
+.ie \\n(.$ \&\\$3\s+2\\$1\s0\\$2
+.el .ps +2
+..
+.		\" NL - return to normal size
+.de NL
+.ps \\n(PS
+..
+.		\" FS - begin footnote
+.de FS
+.if \\n(IF>0 .tm .FS within .FS/.FE, line \\n(.c, file \\n(.F
+.if \\n(KF>0 .tm .FS won't work inside .KF, line \\n(.c, file \\n(.F
+.if \\n(KS>0 .tm .FS won't work inside .KS, line \\n(.c, file \\n(.F
+.nr IF 1
+.ev 1
+.ps \\n(PS-2
+.ie \\n(VS>=41 .vs \\n(VSu-2p
+.el .vs \\n(VSp-2p
+.ll \\n(LLu
+.br
+.nr X \\n(FOu
+.X "BF \\nX FS
+.SP .3v
+....FA	\" deleted by authority of cvw, 10/17/88
+..
+.		\" FE - end footnote
+.de FE
+.if !\\n(IF .tm .FE without .FS, line \\n(.c, file \\n(.F
+.br
+.X "END BF FE
+.bp
+.ev
+.nr IF 0
+..
+.		\" FA - the line for a footnote
+.de FA
+\l'1i'
+.br
+..
+.		\" Tm - message to be passed on
+.de Tm
+.ev 2
+.if \\n(.$=1 .X "TM \\$1
+.if \\n(.$=2 .X "TM \\$1 \\$2
+.if \\n(.$=3 .X "TM \\$1 \\$2 \\$3
+.if \\n(.$=4 .X "TM \\$1 \\$2 \\$3 \\$4
+.if \\n(.$=5 .X "TM \\$1 \\$2 \\$3 \\$4 \\$5
+.if \\n(.$=6 .X "TM \\$1 \\$2 \\$3 \\$4 \\$5 \\$6
+.if \\n(.$=7 .X "TM \\$1 \\$2 \\$3 \\$4 \\$5 \\$6 \\$7
+.if \\n(.$=8 .X "TM \\$1 \\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8
+.if \\n(.$=9 .X "TM \\$1 \\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8 \\$9
+.br
+.ev
+..
+.de MH
+AT&T Bell Laboratories
+Murray Hill, New Jersey 07974
+..
+.de HO
+AT&T Bell Laboratories
+Holmdel, New Jersey 07733
+..
+.de WH
+AT&T Bell Laboratories
+Whippany, New Jersey 07981
+..
+.de IH
+AT&T Bell Laboratories
+Naperville, Illinois 60540
+..
+.		\" UL - underline argument, don't italicize
+.de UL
+\\$1\l'|0\(ul'\\$2
+..
+.		\" UX - print $2 UNIX $1
+.de UX
+.ie \\n(UX \\$2\s-1UNIX\s0\\$1
+.el \{\
+\\$2\s-1UNIX\\s0\\$1\(rg
+.nr UX 1\}
+..
+.		\" QS - start quote
+.de QS
+.br
+.LP
+.in +\\n(QIu
+.ll -\\n(QIu
+..
+.		\" QE - end quote
+.de QE
+.br
+.ll +\\n(QIu
+.in -\\n(QIu
+.LP
+..
+.		\"  B1 - begin boxed stuff
+.de B1
+.br
+.di BB
+.nr BC 0
+.if "\\$1"C" .nr BC 1
+.nr BE 1
+..
+.		\" B2 - end boxed stuff
+.de B2 
+.br
+.nr BI 1n
+.if \\n(.$>0 .nr BI \\$1n
+.di
+.nr BE 0
+.nr BW \\n(dl
+.nr BH \\n(dn
+.ne \\n(BHu+\\n(.Vu
+.nr BQ \\n(.j
+.nf
+.ti 0
+.if \\n(BC>0 .in +(\\n(.lu-\\n(BWu)/2u
+.in +\\n(BIu
+.ls 1
+.BB
+.ls
+.in -\\n(BIu
+.nr BW +2*\\n(BI
+.sp -1
+\l'\\n(BWu\(ul'\L'-\\n(BHu'\l'|0\(ul'\h'|0'\L'\\n(BHu'
+.if \\n(BC>0 .in -(\\n(.lu-\\n(BWu)/2u
+.if \\n(BQ .fi
+.br
+..
+.		\" BX - boxed stuff
+.de BX
+\(br\|\\$1\|\(br\l'|0\(rn'\l'|0\(ul'
+..
+.
+.	\" macros for programs, etc.
+.
+.ig
+	programs are displayed between .P1/.P2 pairs
+	default is to indent by 1/2 inch, nofill, dP smaller
+	.P1 x causes an indent of x instead.
+
+	.P3 can be used to specify optional page-break points
+	inside .P1/.P2
+..
+.
+.		\" P1 - start of program
+.de P1
+.nr $1 \\n(P1
+.if \\n(.$ .nr $1 \\$1n
+.br
+.X "SP \\n(DV P1"
+.X "US P1"
+.in \\n($1u
+.nf
+.nr v \\n(.v
+.ps -\\n(dP
+.vs -\\n(dVu
+.ft CW
+.nr t \\n(dT*\\w'x'u
+.ta 1u*\\ntu 2u*\\ntu 3u*\\ntu 4u*\\ntu 5u*\\ntu 6u*\\ntu 7u*\\ntu 8u*\\ntu 9u*\\ntu 10u*\\ntu 11u*\\ntu 12u*\\ntu 13u*\\ntu 14u*\\ntu
+..
+.		\" P2 - end of program
+.de P2
+.br
+.ps \\n(PS
+.vs \\nvu
+.ft 1
+.in
+.X "END US P1
+.X "SP \\n(DV P2"
+.fi
+..
+.		\" P3 - provides optional unpadded break in P1/P2
+.de P3
+.nr x \\n(DV
+.nr DV 0
+.P2
+.P1 \\n($1u
+.nr DV \\nx
+..
+.de [
+[
+..
+.de ]
+]
+..
+.IZ
+.rm IZ
+.so /sys/lib/tmac/tmac.srefs
blob - d1b64224d24862cd717d3ebe74eb15864ba2a3f4
blob + bec41e47a7d930ec79bd12cdedfc421b2e876bd4
--- src/cmd/pic/arcgen.c
+++ src/cmd/pic/arcgen.c
@@ -11,17 +11,19 @@ obj *arcgen(int type)	/* handles circular and (eventua
 	static double prevw = HT10;
 	static double prevh = HT5;
 	static double prevrad = HT2;
-	static int dtox[2][4] ={ 1, -1, -1, 1, 1, 1, -1, -1 };
-	static int dtoy[2][4] ={ 1, 1, -1, -1, -1, 1, 1, -1 };
-	static int dctrx[2][4] ={ 0, -1, 0, 1, 0, 1, 0, -1 };
-	static int dctry[2][4] ={ 1, 0, -1, 0, -1, 0, 1, 0 };
-	static int nexthv[2][4] ={ U_DIR, L_DIR, D_DIR, R_DIR, D_DIR, R_DIR, U_DIR, L_DIR };
+	static int dtox[2][4] ={ { 1, -1, -1, 1}, {1, 1, -1, -1} };
+	static int dtoy[2][4] ={ {1, 1, -1, -1}, {-1, 1, 1, -1} };
+	static int dctrx[2][4] ={ {0, -1, 0, 1}, {0, 1, 0, -1} };
+	static int dctry[2][4] ={ {1, 0, -1, 0}, {-1, 0, 1, 0} };
+	static int nexthv[2][4] ={ {U_DIR, L_DIR, D_DIR, R_DIR}, {D_DIR, R_DIR, U_DIR, L_DIR} };
 	double dx2, dy2, ht, phi, r, d;
 	int i, head, to, at, cw, invis, ddtype, battr;
 	obj *p, *ppos;
 	double fromx, fromy, tox, toy, fillval = 0;
 	Attr *ap;
 
+	tox=toy=0.0; /* Botch? (gcc) */
+
 	prevrad = getfval("arcrad");
 	prevh = getfval("arrowht");
 	prevw = getfval("arrowwid");
@@ -210,6 +212,7 @@ void arc_extreme(double x0, double y0, double x1, doub
 	extreme(xmax, ymax);
 }
 
+int
 quadrant(double x, double y)
 {
 	if (     x>=0.0 && y> 0.0) return(1);
blob - f40bae9a1e03953f32275fdcdb25f9a1fda18989
blob + 6b1f15486dc20bf2d7d0b6b10c917cb35ebb8560
--- src/cmd/pic/circgen.c
+++ src/cmd/pic/circgen.c
@@ -12,6 +12,8 @@ obj *circgen(int type)
 	obj *p, *ppos;
 	Attr *ap;
 
+	r = r2 = 0.0; /* Botch? (gcc) */
+
 	battr = at = 0;
 	with = xwith = ywith = fillval = ddval = 0;
 	t = (type == CIRCLE) ? 0 : 1;
blob - 875b1868a7838074048ffd5b4d7b883786a38626
blob + bef0c42bd183a93ff74a543268af6a3aa5d2e119
--- src/cmd/pic/input.c
+++ src/cmd/pic/input.c
@@ -26,7 +26,7 @@ void pushsrc(int type, char *ptr)	/* new input source 
 	srcp->type = type;
 	srcp->sp = ptr;
 	if (dbg > 1) {
-		printf("\n%3d ", srcp - src);
+		printf("\n%3d ", (int) (srcp - src));
 		switch (srcp->type) {
 		case File:
 			printf("push file %s\n", ((Infile *)ptr)->fname);
@@ -57,7 +57,7 @@ void popsrc(void)	/* restore an old one */
 	if (srcp <= src)
 		ERROR "too many inputs popped" FATAL;
 	if (dbg > 1) {
-		printf("%3d ", srcp - src);
+		printf("%3d ", (int) (srcp - src));
 		switch (srcp->type) {
 		case File:
 			printf("pop file\n");
@@ -142,6 +142,7 @@ char *delimstr(char *s)	/* get body of X ... X */
 	return tostring(buf);
 }
 
+int
 baldelim(int c, char *s)	/* replace c by balancing entry in s */
 {
 	for ( ; *s; s += 2)
@@ -187,11 +188,12 @@ void dodef(struct symtab *stp)	/* collect args and swi
 		ap->argstk[i] = "";
 	if (dbg)
 		for (i = 0; i < argcnt; i++)
-			printf("arg %d.%d = <%s>\n", ap-args, i+1, ap->argstk[i]);
+			printf("arg %d.%d = <%s>\n", (int) (ap-args), i+1, ap->argstk[i]);
 	argfp = ap;
 	pushsrc(Macro, stp->s_val.p);
 }
 
+int
 getarg(char *p)	/* pick up single argument, store in p, return length */
 {
 	int n, c, npar;
@@ -232,6 +234,7 @@ extern	int	thru;
 extern	struct symtab	*thrudef;
 extern	char	*untilstr;
 
+int
 input(void)
 {
 	register int c;
@@ -248,10 +251,13 @@ input(void)
 	return *ep++ = c;
 }
 
+int
 nextchar(void)
 {
 	register int c;
 
+	c = 0; /* Botch: gcc */
+
   loop:
 	switch (srcp->type) {
 	case Free:	/* free string */
@@ -289,9 +295,9 @@ nextchar(void)
 				ERROR "argfp underflow" FATAL;
 			popsrc();
 			goto loop;
-		} else if (c == '$' && isdigit(*srcp->sp)) {
+		} else if (c == '$' && isdigit((unsigned char) *srcp->sp)) {
 			int n = 0;
-			while (isdigit(*srcp->sp))
+			while (isdigit((unsigned char) *srcp->sp))
 				n = 10 * n + *srcp->sp++ - '0';
 			if (n > 0 && n <= MAXARGS)
 				pushsrc(String, argfp->argstk[n-1]);
@@ -380,7 +386,7 @@ void do_thru(void)	/* read one line, make into a macro
 		ap->argstk[i] = "";
 	if (dbg)
 		for (i = 0; i < argcnt; i++)
-			printf("arg %d.%d = <%s>\n", ap-args, i+1, ap->argstk[i]);
+			printf("arg %d.%d = <%s>\n", (int) (ap-args), i+1, ap->argstk[i]);
 	if (strcmp(ap->argstk[0], ".PE") == 0) {
 		thru = 0;
 		thrudef = 0;
@@ -400,6 +406,7 @@ void do_thru(void)	/* read one line, make into a macro
 	pushsrc(Macro, thrudef->s_val.p);
 }
 
+int
 unput(int c)
 {
 	if (++pb >= pbuf + sizeof pbuf)
@@ -580,7 +587,7 @@ void shell_init(void)	/* set up to interpret a shell c
 
 void shell_text(char *s)	/* add string to command being collected */
 {
-	while (*shellp++ = *s++)
+	while ((*shellp++ = *s++))
 		;
 	shellp--;
 }
blob - ab9966503475b451b9dd675433f397e3c6e9eab1
blob + 1c7f5f655f440113dac0a0d4d9df76bd49499bad
--- src/cmd/pic/main.c
+++ src/cmd/pic/main.c
@@ -47,6 +47,7 @@ void	getdata(void), setdefaults(void);
 void	setfval(char *, double);
 int	getpid(void);
 
+int
 main(int argc, char *argv[])
 {
 	char buf[20];
@@ -120,27 +121,27 @@ static struct {
 	double val;
 	short scalable;		/* 1 => adjust when "scale" changes */
 } defaults[] ={
-	"scale", SCALE, 1,
-	"lineht", HT, 1,
-	"linewid", HT, 1,
-	"moveht", HT, 1,
-	"movewid", HT, 1,
-	"dashwid", HT10, 1,
-	"boxht", HT, 1,
-	"boxwid", WID, 1,
-	"circlerad", HT2, 1,
-	"arcrad", HT2, 1,
-	"ellipseht", HT, 1,
-	"ellipsewid", WID, 1,
-	"arrowht", HT5, 1,
-	"arrowwid", HT10, 1,
-	"arrowhead", 2, 0,		/* arrowhead style */
-	"textht", 0.0, 1,		/* 6 lines/inch is also a useful value */
-	"textwid", 0.0, 1,
-	"maxpsht", MAXHT, 0,
-	"maxpswid", MAXWID, 0,
-	"fillval", 0.7, 0,		/* gray value for filling boxes */
-	NULL, 0, 0
+	{ "scale", SCALE, 1, },
+	{ "lineht", HT, 1, },
+	{ "linewid", HT, 1, },
+	{ "moveht", HT, 1, },
+	{ "movewid", HT, 1, },
+	{ "dashwid", HT10, 1, },
+	{ "boxht", HT, 1, },
+	{ "boxwid", WID, 1, },
+	{ "circlerad", HT2, 1, },
+	{ "arcrad", HT2, 1, },
+	{ "ellipseht", HT, 1, },
+	{ "ellipsewid", WID, 1, },
+	{ "arrowht", HT5, 1, },
+	{ "arrowwid", HT10, 1, },
+	{ "arrowhead", 2, 0, },		/* arrowhead style */
+	{ "textht", 0.0, 1, },		/* 6 lines/inch is also a useful value */
+	{ "textwid", 0.0, 1, },
+	{ "maxpsht", MAXHT, 0, },
+	{ "maxpswid", MAXWID, 0, },
+	{ "fillval", 0.7, 0, },		/* gray value for filling boxes */
+	{ NULL, 0, 0 }
 };
 
 void setdefaults(void)	/* set default sizes for variables like boxht */
blob - 0a33c67229ace61f5e93d95457b8ba0536340b4b
blob + 1ada1c1c365575f380684d1ab66599484c8bf9c2
--- src/cmd/pic/misc.c
+++ src/cmd/pic/misc.c
@@ -9,6 +9,7 @@ int whatpos(obj *p, int corner, double *px, double *py
 void makeattr(int type, int sub, YYSTYPE val);
 YYSTYPE getblk(obj *, char *);
 
+int
 setdir(int n)	/* set direction (hvmode) from LEFT, RIGHT, etc. */
 {
 	switch (n) {
@@ -20,6 +21,7 @@ setdir(int n)	/* set direction (hvmode) from LEFT, RIG
  	return(hvmode);
 }
 
+int
 curdir(void)	/* convert current dir (hvmode) to RIGHT, LEFT, etc. */
 {
 	switch (hvmode) {
@@ -32,7 +34,8 @@ curdir(void)	/* convert current dir (hvmode) to RIGHT,
 	return 0;
 }
 
-double getcomp(obj *p, int t)	/* return component of a position */
+double 
+getcomp(obj *p, int t)	/* return component of a position */
 {
 	switch (t) {
 	case DOTX:
@@ -207,7 +210,9 @@ int whatpos(obj *p, int corner, double *px, double *py
 {
 	double x, y, x1, y1;
 
-	dprintf("whatpos %o %d %d\n", p, p->o_type, corner);
+	x1 = y1 = 0.0; /* Botch? (gcc) */
+
+	dprintf("whatpos %p %d %d\n", p, p->o_type, corner);
 	x = p->o_x;
 	y = p->o_y;
 	if (p->o_type != PLACE && p->o_type != MOVE) {
@@ -320,7 +325,7 @@ obj *getlast(int n, int t)	/* find n-th previous occur
 		dprintf("got a last of x,y= %g,%g\n", p->o_x, p->o_y);
 		return(p);
 	}
-	ERROR "there is no %dth last", n FATAL;
+	ERROR "there is no %dth last", n WARNING;
 	return(NULL);
 }
 
@@ -343,7 +348,7 @@ obj *getfirst(int n, int t)	/* find n-th occurrence of
 		dprintf("got a first of x,y= %g,%g\n", p->o_x, p->o_y);
 		return(p);
 	}
-	ERROR "there is no %dth ", n FATAL;
+	ERROR "there is no %dth ", n WARNING;
 	return(NULL);
 }
 
blob - dbe2a3d5c86c3a18b1c66c9383ec360a630d7a22
blob + f5a9c15525608786571b707d2788b2a738ce16ab
--- src/cmd/pic/picl.lx
+++ src/cmd/pic/picl.lx
@@ -133,7 +133,7 @@ WS	[ \t]
 <A>ccw		{ yylval.i = CCW; return(ATTR); }
 <A>invis(ible)?	{ yylval.i = INVIS; return(ATTR); }
 <A>noedge	{ yylval.i = INVIS; return ATTR; }
-<A>fill		return(yylval.i = FILL);
+<A>fill		{ yylval.i = FILL; return ATTR; }
 <A>solid	;
 <A>dot(ted)?	return(yylval.i = DOT);
 <A>dash(ed)?	return(yylval.i = DASH);
blob - ad21e0d70ed99b3df401e6156a36c425061934f7
blob + 6cf5103d2a85265f5d5949eb378b85a984465f9d
--- src/cmd/pic/print.c
+++ src/cmd/pic/print.c
@@ -25,6 +25,8 @@ void print(void)
 	int fill, vis, invis;
 	double x0, y0, x1, y1, ox, oy, dx, dy, ndx, ndy;
 
+	x1 = y1 = 0.0; /* Botch? (gcc) */
+
 	for (i = 0; i < nobj; i++) {
 		p = objlist[i];
 		ox = p->o_x;
@@ -180,6 +182,8 @@ void dotline(double x0, double y0, double x1, double y
 	int i, numdots;
 	double a, b, dx, dy;
 
+	b = 0.0; /* Botch? (gcc) */
+
 	if (ddval == 0)
 		ddval = prevval;
 	prevval = ddval;
blob - 879075c6854b3083ccc2f4f09e530bbc0151a8c5
blob + 059d3be7b852d2568da47f04cfddf2a7d7e66126
--- src/cmd/pic/symtab.c
+++ src/cmd/pic/symtab.c
@@ -12,7 +12,7 @@ YYSTYPE getvar(char *s)	/* return value of variable s 
 
 	p = lookup(s);
 	if (p == NULL) {
-		if (islower(s[0]))
+		if (islower((int) s[0]))
 			ERROR "no such variable as %s", s WARNING;
 		else
 			ERROR "no such place as %s", s WARNING;