aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/mpm
diff options
context:
space:
mode:
authorwkj <devnull@localhost>2004-05-16 07:56:41 +0000
committerwkj <devnull@localhost>2004-05-16 07:56:41 +0000
commit5f1cf8e6fb130fd48d6f016d13baf5408b3181f8 (patch)
tree7f77f458df8c8b34db139fb4551df602ab6286be /src/cmd/mpm
parentc5561c23cf394806cbf6d70a96f2dc0253f93745 (diff)
downloadplan9port-5f1cf8e6fb130fd48d6f016d13baf5408b3181f8.tar.gz
plan9port-5f1cf8e6fb130fd48d6f016d13baf5408b3181f8.tar.bz2
plan9port-5f1cf8e6fb130fd48d6f016d13baf5408b3181f8.zip
Checkpoint: pull in mpm; merge pic from Taj's version of the world
Diffstat (limited to 'src/cmd/mpm')
-rw-r--r--src/cmd/mpm/.cvsignore1
-rw-r--r--src/cmd/mpm/README188
-rw-r--r--src/cmd/mpm/misc.cc12
-rw-r--r--src/cmd/mpm/misc.h41
-rw-r--r--src/cmd/mpm/mkfile24
-rw-r--r--src/cmd/mpm/page.cc612
-rw-r--r--src/cmd/mpm/page.h119
-rw-r--r--src/cmd/mpm/queue.cc235
-rw-r--r--src/cmd/mpm/range.cc613
-rw-r--r--src/cmd/mpm/range.h334
-rw-r--r--src/cmd/mpm/slug.cc603
-rw-r--r--src/cmd/mpm/slug.h74
-rw-r--r--src/cmd/mpm/tmac.pm961
13 files changed, 3817 insertions, 0 deletions
diff --git a/src/cmd/mpm/.cvsignore b/src/cmd/mpm/.cvsignore
new file mode 100644
index 00000000..a45a32ad
--- /dev/null
+++ b/src/cmd/mpm/.cvsignore
@@ -0,0 +1 @@
+pm
diff --git a/src/cmd/mpm/README b/src/cmd/mpm/README
new file mode 100644
index 00000000..5433ccc3
--- /dev/null
+++ b/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.
diff --git a/src/cmd/mpm/misc.cc b/src/cmd/mpm/misc.cc
new file mode 100644
index 00000000..e0f303ad
--- /dev/null
+++ b/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
diff --git a/src/cmd/mpm/misc.h b/src/cmd/mpm/misc.h
new file mode 100644
index 00000000..682964b0
--- /dev/null
+++ b/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
diff --git a/src/cmd/mpm/mkfile b/src/cmd/mpm/mkfile
new file mode 100644
index 00000000..635edafa
--- /dev/null
+++ b/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
diff --git a/src/cmd/mpm/page.cc b/src/cmd/mpm/page.cc
new file mode 100644
index 00000000..966edae9
--- /dev/null
+++ b/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 */
+}
diff --git a/src/cmd/mpm/page.h b/src/cmd/mpm/page.h
new file mode 100644
index 00000000..dcccbe0c
--- /dev/null
+++ b/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 **);
diff --git a/src/cmd/mpm/queue.cc b/src/cmd/mpm/queue.cc
new file mode 100644
index 00000000..d065ac82
--- /dev/null
+++ b/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();
+}
diff --git a/src/cmd/mpm/range.cc b/src/cmd/mpm/range.cc
new file mode 100644
index 00000000..988dff2f
--- /dev/null
+++ b/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();
+ }
+}
diff --git a/src/cmd/mpm/range.h b/src/cmd/mpm/range.h
new file mode 100644
index 00000000..401035a5
--- /dev/null
+++ b/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 *);
diff --git a/src/cmd/mpm/slug.cc b/src/cmd/mpm/slug.cc
new file mode 100644
index 00000000..14378b90
--- /dev/null
+++ b/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;
+ }
+ }
+}
diff --git a/src/cmd/mpm/slug.h b/src/cmd/mpm/slug.h
new file mode 100644
index 00000000..9dfd3b2a
--- /dev/null
+++ b/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 *);
diff --git a/src/cmd/mpm/tmac.pm b/src/cmd/mpm/tmac.pm
new file mode 100644
index 00000000..7ce01466
--- /dev/null
+++ b/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