From cse.psu.edu!9fans-admin Thu Feb 14 12:53:21 EST 2002 # To unbundle, run this file echo README sed 's/.//' >README <<'//GO.SYSIN DD README' -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 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. //GO.SYSIN DD README echo misc.c sed 's/.//' >misc.c <<'//GO.SYSIN DD misc.c' -#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 //GO.SYSIN DD misc.c echo misc.h sed 's/.//' >misc.h <<'//GO.SYSIN DD misc.h' -#include -#include -#include -#include -#include - -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 max(int x, int y) { return x > y ? x : y; } -inline min(int x, int y) { return x > y ? y : x; } -inline 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 //GO.SYSIN DD misc.h echo mkfile sed 's/.//' >mkfile <<'//GO.SYSIN DD mkfile' -page.c <<'//GO.SYSIN DD page.c' -#include "misc.h" -#include "slug.h" -#include "range.h" -#include "page.h" - -const 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) -{ - const int Huge = 100000; - for (int 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 (int 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()) - if (r->isvbox() || r->issp()) - break; - return r; -} - -// Zero out leading and trailing spaces; coalesce adjacent SP's. -static void trimspace(stream *scratch) -{ - range *r, *prevr = 0; - for (generator 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. - for (generator g = scratch; r = g.next(); ) - 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(); ) - 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; - for (stream 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 - for (stream 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 rawht = scratch.rawht(); - int halfheight = (int)(coltol*rawht); - // choose a goal height - int maxht = defonly ? halfheight : htavail; -secondtry: - for (int 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(); - for (stream 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 -} - -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); -} //GO.SYSIN DD page.c echo page.h sed 's/.//' >page.h <<'//GO.SYSIN DD page.h' -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 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 mergestream; - multicol *twocol; - friend 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 -main(int, char **); //GO.SYSIN DD page.h echo queue.c sed 's/.//' >queue.c <<'//GO.SYSIN DD queue.c' -#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(); -} //GO.SYSIN DD queue.c echo range.c sed 's/.//' >range.c <<'//GO.SYSIN DD range.c' -#include -#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() -{ - stream s = *this; - for (int h = 0; s.more(); s.advance()) - h += s.current()->height(); - return h; -} - -int stream::rawht() -{ - stream s = *this; - for (int 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()) - 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) -{ - for (slug *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 *prevV = 0; - for (slug *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(); - } -} //GO.SYSIN DD range.c echo range.h sed 's/.//' >range.h <<'//GO.SYSIN DD range.h' -const 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 *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 *); //GO.SYSIN DD range.h echo slug.c sed 's/.//' >slug.c <<'//GO.SYSIN DD slug.c' -#include "misc.h" -#include "slug.h" -#include -#include - -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 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 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 ")) - 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 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 = (x0x1)?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 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; - break; - } - } -} //GO.SYSIN DD slug.c echo slug.h sed 's/.//' >slug.h <<'//GO.SYSIN DD slug.h' -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 *); //GO.SYSIN DD slug.h echo tmac.pm sed 's/.//' >tmac.pm <<'//GO.SYSIN DD tmac.pm' -.\" 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 //GO.SYSIN DD tmac.pm