diff options
author | wkj <devnull@localhost> | 2004-05-16 07:56:41 +0000 |
---|---|---|
committer | wkj <devnull@localhost> | 2004-05-16 07:56:41 +0000 |
commit | 5f1cf8e6fb130fd48d6f016d13baf5408b3181f8 (patch) | |
tree | 7f77f458df8c8b34db139fb4551df602ab6286be /src/cmd/mpm | |
parent | c5561c23cf394806cbf6d70a96f2dc0253f93745 (diff) | |
download | plan9port-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/.cvsignore | 1 | ||||
-rw-r--r-- | src/cmd/mpm/README | 188 | ||||
-rw-r--r-- | src/cmd/mpm/misc.cc | 12 | ||||
-rw-r--r-- | src/cmd/mpm/misc.h | 41 | ||||
-rw-r--r-- | src/cmd/mpm/mkfile | 24 | ||||
-rw-r--r-- | src/cmd/mpm/page.cc | 612 | ||||
-rw-r--r-- | src/cmd/mpm/page.h | 119 | ||||
-rw-r--r-- | src/cmd/mpm/queue.cc | 235 | ||||
-rw-r--r-- | src/cmd/mpm/range.cc | 613 | ||||
-rw-r--r-- | src/cmd/mpm/range.h | 334 | ||||
-rw-r--r-- | src/cmd/mpm/slug.cc | 603 | ||||
-rw-r--r-- | src/cmd/mpm/slug.h | 74 | ||||
-rw-r--r-- | src/cmd/mpm/tmac.pm | 961 |
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 |