|  | #include	"misc.h" | 
|  | #include	"slug.h" | 
|  | #include	"range.h" | 
|  | #include	"page.h" | 
|  |  | 
|  | const int	MAXRANGES	= 1000; | 
|  | static range *ptemp[MAXRANGES];		// for movefloats() | 
|  |  | 
|  | static void swapright(int n)		// used by movefloats() | 
|  | { | 
|  | range *t = ptemp[n]; | 
|  | ptemp[n] = ptemp[n+1]; | 
|  | ptemp[n+1] = t; | 
|  | ptemp[n]->setaccum( ptemp[n+1]->accum() - | 
|  | ptemp[n+1]->rawht() + ptemp[n]->rawht() ); | 
|  | ptemp[n+1]->setaccum( ptemp[n]->accum() + ptemp[n+1]->rawht() ); | 
|  | } | 
|  |  | 
|  | // Figure out the goal position for each floating range on scratch, | 
|  | // and move it past stream ranges until it's as close to its goal as possible. | 
|  | static void movefloats(stream *scratch, double scale) | 
|  | { | 
|  | int nranges, i; | 
|  | const int Huge = 100000; | 
|  |  | 
|  | for (nranges = 0; scratch->more(); scratch->advance()) | 
|  | ptemp[nranges++] = scratch->current(); | 
|  | scratch->freeall(); | 
|  | ufrange rtemp; | 
|  | ptemp[nranges] = &rtemp; | 
|  | rtemp.setgoal(Huge); | 
|  | int accumV = 0;				// compute accum values and | 
|  | for (i = 0; i < nranges; i++) {		// pick closest goal for floats | 
|  | ptemp[i]->pickgoal(accumV, scale); | 
|  | ptemp[i]->setaccum(accumV += ptemp[i]->rawht()); | 
|  | } | 
|  | int j;					// index for inner loop below: | 
|  | for (i = nranges; --i >= 0; )		// stably sort floats to bottom | 
|  | for (j = i; j < nranges; j++) | 
|  | if (ptemp[j]->goal() > ptemp[j+1]->goal()) | 
|  | swapright(j); | 
|  | else | 
|  | break; | 
|  | if (dbg & 16) | 
|  | printf("#movefloats:  before floating, from bottom:\n"); | 
|  | for (i = nranges; --i >= 0; ) {		// find topmost float | 
|  | if (ptemp[i]->goal() == NOGOAL) | 
|  | break; | 
|  | if (dbg & 16) | 
|  | printf("# serialno %d goal %d height %d\n", | 
|  | ptemp[i]->serialno(), ptemp[i]->goal(), | 
|  | ptemp[i]->rawht()); | 
|  | }					// i+1 is topmost float | 
|  | for (i++ ; i < nranges; i++)		// move each float up the page | 
|  | for (j = i; j > 0; j--)		// as long as closer to its goal | 
|  | if (ptemp[j]->goal() | 
|  | <= ptemp[j-1]->accum() + ptemp[j]->rawht()/2 | 
|  | && ptemp[j-1]->goal() == NOGOAL) | 
|  | swapright(j-1); | 
|  | else | 
|  | break; | 
|  | if (ptemp[nranges] != &rtemp) | 
|  | ERROR "goal sentinel has disappeared from movefloats" FATAL; | 
|  | for (i = 0; i < nranges; i++)		// copy sorted list back | 
|  | scratch->append(ptemp[i]); | 
|  | } | 
|  |  | 
|  | // Traverse the leaves of a tree of ranges, filtering out only SP and VBOX. | 
|  | static range *filter(generator *g) | 
|  | { | 
|  | range *r; | 
|  | while ((r = g->next()) != 0) | 
|  | if (r->isvbox() || r->issp()) | 
|  | break; | 
|  | return r; | 
|  | } | 
|  |  | 
|  | // Zero out leading and trailing spaces; coalesce adjacent SP's. | 
|  | static void trimspace(stream *scratch) | 
|  | { | 
|  | generator g; | 
|  | range *r, *prevr = 0; | 
|  |  | 
|  | for (g = scratch; (r = filter(&g)) != 0 && r->issp(); prevr = r) | 
|  | r->setheight(0);		// zap leading SP | 
|  | for ( ; (r = filter(&g)) != 0; prevr = r) | 
|  | if (r->issp()) | 
|  | if (prevr && prevr->issp()) { | 
|  | // coalesce adjacent SPs | 
|  | r->setheight(max(r->rawht(), prevr->height())); | 
|  | prevr->setheight(0); | 
|  | } else			// a VBOX intervened | 
|  | r->setheight(r->rawht()); | 
|  | if (prevr && prevr->issp())		// zap *all* trailing space | 
|  | prevr->setheight(0);		// (since it all coalesced | 
|  | // into the last one) | 
|  | } | 
|  |  | 
|  | // Pad the non-zero SP's in scratch so the total height is wantht. | 
|  | // Note that the SP values in scratch are not the raw values, and | 
|  | // indeed may already have been padded. | 
|  | static void justify(stream *scratch, int wantht) | 
|  | { | 
|  | range *r; | 
|  | int nsp = 0, hsp = 0; | 
|  |  | 
|  | int adjht = scratch->height(); | 
|  | // Find all the spaces. | 
|  | generator g; | 
|  | for (g = scratch; (r = g.next()) != 0; ) | 
|  | if (r->issp() && r->height() > 0) { | 
|  | nsp++; | 
|  | hsp += r->height(); | 
|  | } | 
|  | int excess = wantht - adjht; | 
|  | if (excess < 0) | 
|  | ERROR "something on page %d is oversize by %d\n", | 
|  | userpn, -excess WARNING; | 
|  | if (dbg & 16) | 
|  | printf("# justify %d: excess %d nsp %d hsp %d adjht %d\n", | 
|  | userpn, excess, nsp, hsp, adjht); | 
|  | if (excess <= 0 || nsp == 0) | 
|  | return; | 
|  | // Redistribute the excess space. | 
|  | for (g = scratch; (r = g.next()) != 0; ) | 
|  | if (r->issp() && r->height() > 0) { | 
|  | int delta = (int) ((float)(r->height()*excess)/hsp + 0.5); | 
|  | if (dbg & 16) | 
|  | printf("# pad space %d by %d: hsp %d excess %d\n", | 
|  | r->height(), delta, hsp, excess); | 
|  | r->setheight(r->height() + delta); | 
|  | } | 
|  | } | 
|  |  | 
|  | // If r were added to s, would the height of the composed result be at most maxht? | 
|  | int wouldfit(range *r, stream *s, int maxht) | 
|  | { | 
|  | if (r->rawht() + s->rawht() <= maxht) | 
|  | return 1;		// the conservative test succeeded | 
|  | stream scratch;			// local playground for costly test | 
|  | for (stream cd = *s; cd.more(); cd.advance()) | 
|  | scratch.append(cd.current()); | 
|  | scratch.append(r); | 
|  | movefloats(&scratch, ((double) scratch.rawht())/maxht); | 
|  | trimspace(&scratch); | 
|  | int retval = scratch.height() <= maxht; | 
|  | scratch.freeall(); | 
|  | return retval; | 
|  | } | 
|  |  | 
|  | // If s1 were added to s, would the height of the composed result be at most maxht? | 
|  | // The computational structure is similar to that above. | 
|  | int wouldfit(stream *s1, stream *s, int maxht) | 
|  | { | 
|  | if (s1->rawht() + s->rawht() <= maxht) | 
|  | return 1; | 
|  | stream scratch, cd; | 
|  | for (cd = *s; cd.more(); cd.advance()) | 
|  | scratch.append(cd.current()); | 
|  | for (cd = *s1; cd.more(); cd.advance()) | 
|  | scratch.append(cd.current()); | 
|  | movefloats(&scratch, ((double) scratch.rawht())/maxht); | 
|  | trimspace(&scratch); | 
|  | int retval = scratch.height() <= maxht; | 
|  | scratch.freeall(); | 
|  | return retval; | 
|  | } | 
|  |  | 
|  | // All of stream *s is destined for one column or the other; which is it to be? | 
|  | void multicol::choosecol(stream *s, int goalht) | 
|  | { | 
|  | stream *dest; | 
|  | if (!leftblocked && wouldfit(s, &(column[0]), goalht)) | 
|  | dest = &(column[0]); | 
|  | else { | 
|  | dest = &(column[1]); | 
|  | if (!s->current()->floatable()) | 
|  | // a stream item is going into the right | 
|  | // column, so no more can go into the left. | 
|  | leftblocked = 1; | 
|  | } | 
|  | for (stream cd = *s; cd.more(); cd.advance()) | 
|  | dest->append(cd.current()); | 
|  | } | 
|  |  | 
|  | double coltol = 0.5; | 
|  |  | 
|  | // Try, very hard, to put everything in the multicol into two columns | 
|  | // so that the total height is at most htavail. | 
|  | void multicol::compose(int defonly) | 
|  | { | 
|  | if (!nonempty()) { | 
|  | setheight(0); | 
|  | return; | 
|  | } | 
|  | scratch.freeall();	// fill scratch with everything destined | 
|  | // for either column | 
|  | stream cd; | 
|  | for (cd = definite; cd.more(); cd.advance()) | 
|  | scratch.append(cd.current()); | 
|  | if (!defonly) | 
|  | for (cd = *(currpage->stage); cd.more(); cd.advance()) | 
|  | if (cd.current()->numcol() == 2) | 
|  | scratch.append(cd.current()); | 
|  | scratch.restoreall();		// in particular, floatables' goals | 
|  | int i; | 
|  | int rawht = scratch.rawht(); | 
|  | int halfheight = (int)(coltol*rawht); | 
|  | // choose a goal height | 
|  | int maxht = defonly ? halfheight : htavail; | 
|  | secondtry: | 
|  | for (i = 0; i < 2; i++) | 
|  | column[i].freeall(); | 
|  | leftblocked = 0; | 
|  | cd = scratch; | 
|  | while (cd.more()) { | 
|  | queue ministage;	// for the minimally acceptable chunks | 
|  | ministage.freeall();	// that are to be added to either column | 
|  | while (cd.more() && !cd.current()->issentinel()) { | 
|  | ministage.enqueue(cd.current()); | 
|  | cd.advance(); | 
|  | } | 
|  | choosecol(&ministage, maxht); | 
|  | if (cd.more() && cd.current()->issentinel()) | 
|  | cd.advance();	// past sentinel | 
|  | } | 
|  | if (height() > htavail && maxht != htavail) { | 
|  | // We tried to balance the columns, but | 
|  | // the result was too tall.  Go back | 
|  | // and try again with the less ambitious | 
|  | // goal of fitting the space available. | 
|  | maxht = htavail; | 
|  | goto secondtry; | 
|  | } | 
|  | for (i = 0; i < 2; i++) { | 
|  | movefloats(&(column[i]), ((double) column[i].rawht())/currpage->pagesize); | 
|  | trimspace(&(column[i])); | 
|  | } | 
|  | if (dbg & 32) { | 
|  | printf("#multicol::compose: htavail %d maxht %d dv %d\n", | 
|  | htavail, maxht, height()); | 
|  | dump(); | 
|  | } | 
|  | if (defonly) | 
|  | stretch(height()); | 
|  | } | 
|  |  | 
|  | // A sequence of two-column ranges waits on the stage. | 
|  | // So long as the page's skeleton hasn't changed--that is, the maximum height | 
|  | // available to the two-column chunk is the same--we just use the columns that | 
|  | // have been built up so far, and choose a column into which to put the stage. | 
|  | // If the skeleton has changed, however, then we may need to make entirely | 
|  | // new decisions about which column gets what, so we recompose the whole page. | 
|  | void multicol::tryout() | 
|  | { | 
|  | if (htavail == prevhtavail) | 
|  | choosecol(currpage->stage, htavail); | 
|  | else | 
|  | currpage->compose(DRAFT); | 
|  | prevhtavail = htavail; | 
|  | } | 
|  |  | 
|  | // Make both columns the same height. | 
|  | // (Maybe this should also be governed by minfull, | 
|  | // to prevent padding very underfull columns.) | 
|  | void multicol::stretch(int wantht) | 
|  | { | 
|  | if (wantht < height()) | 
|  | ERROR "page %d: two-column chunk cannot shrink\n", userpn FATAL; | 
|  | for (int i = 0; i < 2; i++) | 
|  | justify(&(column[i]), wantht); | 
|  | if (dbg & 16) | 
|  | printf("#col hts: left %d right %d\n", | 
|  | column[0].height(), column[1].height()); | 
|  | } | 
|  |  | 
|  | // Report an upper bound on how tall the current two-column object is. | 
|  | // The (possibly composed) heights of the two columns give a crude upper | 
|  | // bound on the total height.  If the result is more than the height | 
|  | // available for the two-column object, then the columns are each | 
|  | // composed to give a better estimate of their heights. | 
|  | int multicol::height() | 
|  | { | 
|  | int retval = max(column[0].height(), column[1].height()); | 
|  | if (retval < htavail) | 
|  | return retval; | 
|  | for (int i = 0; i < 2; i++) { | 
|  | movefloats(&(column[i]), ((double) column[i].height())/currpage->pagesize); | 
|  | trimspace(&(column[i])); | 
|  | } | 
|  | return max(column[0].height(), column[1].height()); | 
|  | } | 
|  |  | 
|  | void multicol::dump() | 
|  | { | 
|  | printf("####2COL dv %d\n", height()); | 
|  | printf("# left column:\n"); | 
|  | column[0].dump(); | 
|  | printf("# right column:\n"); | 
|  | column[1].dump(); | 
|  | } | 
|  |  | 
|  | // From the head of queue qp, peel off a piece whose raw height is at most space. | 
|  | int peeloff(stream *qp, int space) | 
|  | { | 
|  | stream *s1 = qp->current()->children(); | 
|  | if (!(s1 && s1->more() && s1->current()->height() <= space)) | 
|  | // in other words, either qp's head is | 
|  | // not nested, or its first subrange | 
|  | return 0;		// is also too big, so we give up | 
|  | qp->split(); | 
|  | s1 = qp->current()->children(); | 
|  | stream *s2 = qp->next()->children(); | 
|  | while (s2->more() && s2->current()->rawht() <= space) { | 
|  | s1->append(s2->current()); | 
|  | space -= s2->current()->rawht(); | 
|  | s2->advance(); | 
|  | } | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | // There are four possibilities for consecutive calls to tryout(). | 
|  | // If we're processing a sequence of single-column ranges, tryout() | 
|  | // uses the original algorithm: (1) conservative test; (2) costly test; | 
|  | // (3) split a breakable item. | 
|  | // If we're processing a sequence of double-column ranges, tryout() | 
|  | // defers to twocol->tryout(), which gradually builds up the contents | 
|  | // of the two columns until they're as tall as they can be without | 
|  | // exceeding twocol->htavail. | 
|  | // If we're processing a sequence of single-column ranges and we | 
|  | // get a double-column range, then we use compose() to build a | 
|  | // skeleton page and set twocol->htavail, the maximum height that | 
|  | // should be occupied by twocol. | 
|  | // If we're processing a sequence of double-column ranges and we | 
|  | // get a single-column range, then we should go back and squish | 
|  | // the double-column chunk as short as possible before we see if | 
|  | // we can fit the single-column range. | 
|  | void page::tryout() | 
|  | { | 
|  | if (!stage->more()) | 
|  | ERROR "empty stage in page::tryout()\n" FATAL; | 
|  | int curnumcol = stage->current()->numcol(); | 
|  | if (dbg & 32) { | 
|  | printf("#page::tryout(): ncol = %d, prevncol = %d; on stage:\n", | 
|  | curnumcol, prevncol); | 
|  | stage->dump(); | 
|  | printf("#END of stage contents\n"); | 
|  | } | 
|  | switch(curnumcol) { | 
|  | default: | 
|  | ERROR "unexpected number of columns in tryout(): %d\n", | 
|  | stage->current()->numcol() FATAL; | 
|  | break; | 
|  | case 1: | 
|  | if (prevncol == 2) | 
|  | compose(FINAL); | 
|  | if (wouldfit(stage, &definite, pagesize - twocol->height())) | 
|  | commit(); | 
|  | else if (stage->current()->breakable() || blank() | 
|  | && peeloff(stage, | 
|  | pagesize - (definite.height() + twocol->height()))) { | 
|  | // first add the peeled-off part that fits | 
|  | adddef(stage->dequeue()); | 
|  | // then send the rest back for later | 
|  | stage->current()->setbreaking(); | 
|  | welsh(); | 
|  | } else if (blank()) { | 
|  | stage->current()->rdump(); | 
|  | ERROR "A %s is too big to continue.\n", | 
|  | stage->current()->typename() FATAL; | 
|  | } else | 
|  | welsh(); | 
|  | break; | 
|  | case 2: | 
|  | if (prevncol == 1) | 
|  | compose(DRAFT); | 
|  | else | 
|  | twocol->tryout(); | 
|  | if (scratch.height() <= pagesize) | 
|  | commit(); | 
|  | else | 
|  | welsh(); | 
|  | break; | 
|  | } | 
|  | prevncol = curnumcol; | 
|  | } | 
|  |  | 
|  | // To compose the page, we (1) fill scratch with the stuff that's meant to | 
|  | // go on the page; (2) compose scratch as best we can; (3) set the maximum | 
|  | // height available to the two-column part of the page; (4) have the two- | 
|  | // column part compose itself. | 
|  | // In the computation of twocol->htavail, it does not matter that | 
|  | // twocol->height() is merely an upper bound, because it is merely being | 
|  | // subtracted out to give the exact total height of the single-column stuff. | 
|  | void page::compose(int final) | 
|  | { | 
|  | makescratch(final); | 
|  | int adjht = scratch.rawht(); | 
|  | if (dbg & 16) | 
|  | printf("# page %d measure %d\n", userpn, adjht); | 
|  | movefloats(&scratch, ((double) adjht)/pagesize); | 
|  | trimspace(&scratch); | 
|  | twocol->htavail = pagesize - (scratch.height() - twocol->height()); | 
|  | twocol->compose(final); | 
|  | adjht = scratch.height(); | 
|  | if (dbg & 16) | 
|  | printf("# page %d measure %d after trim\n", userpn, adjht); | 
|  | } | 
|  |  | 
|  | // Fill the scratch area with ranges destined for the page. | 
|  | // If defonly == 0, then add anything that's on stage--this is a trial run. | 
|  | // If defonly != 0, use only what's definitely on the page. | 
|  | void page::makescratch(int defonly) | 
|  | { | 
|  | scratch.freeall(); | 
|  | stream cd; | 
|  | for (cd = definite; cd.more(); cd.advance()) | 
|  | scratch.append(cd.current()); | 
|  | if (!defonly) | 
|  | for (cd = *stage; cd.more(); cd.advance()) | 
|  | if (cd.current()->numcol() == 1) | 
|  | scratch.append(cd.current()); | 
|  | if (twocol->nonempty()) | 
|  | scratch.append(twocol); | 
|  | } | 
|  |  | 
|  | // Accept the current contents of the stage. | 
|  | // If the stage contains two-column ranges, add a sentinel to indicate the end | 
|  | // of a chunk of stage contents. | 
|  | void page::commit() | 
|  | { | 
|  | if (dbg & 4) | 
|  | printf("#entering page::commit()\n"); | 
|  | int numcol = 0; | 
|  | while (stage->more()) { | 
|  | numcol = stage->current()->numcol(); | 
|  | adddef(stage->dequeue()); | 
|  | } | 
|  | if (numcol == 2) | 
|  | adddef(new sentrange); | 
|  | } | 
|  |  | 
|  | // Send the current contents of the stage back to its source. | 
|  | void page::welsh() | 
|  | { | 
|  | if (dbg & 4) | 
|  | printf("#entering page::welsh()\n"); | 
|  | while (stage->more()) { | 
|  | range *r = stage->dequeue(); | 
|  | r->enqueue(ANDBLOCK); | 
|  | } | 
|  | } | 
|  |  | 
|  | enum { USonly = 1 }; | 
|  |  | 
|  | // So long as anything is eligible to go onto the page, keep trying. | 
|  | // Once nothing is eligible, compose and justify the page. | 
|  | void page::fill() | 
|  | { | 
|  | while (stage->prime()) | 
|  | stage->pend(); | 
|  | compose(FINAL); | 
|  | if (dbg & 16) | 
|  | scratch.dump(); | 
|  | if (anymore()) { | 
|  | int adjht = scratch.height(); | 
|  | if (adjht > minfull*pagesize) { | 
|  | justify(&scratch, pagesize); | 
|  | adjht = scratch.height(); | 
|  | int stretchamt = max(pagesize - adjht, 0); | 
|  | twocol->stretch(twocol->height() + stretchamt); | 
|  | // in case the page's stretchability lies | 
|  | // entirely in its two-column part | 
|  | } else | 
|  | ERROR "page %d only %.0f%% full; will not be adjusted\n", | 
|  | userpn, 100*(double) adjht/pagesize WARNING; | 
|  | } | 
|  | } | 
|  |  | 
|  | void page::adddef(range *r) | 
|  | { | 
|  | if (dbg & 4) | 
|  | printf("#entering page::adddef()\n"); | 
|  | switch (r->numcol()) { | 
|  | case 1:	definite.append(r); | 
|  | break; | 
|  | case 2: twocol->definite.append(r); | 
|  | break; | 
|  | default: ERROR "%d-column range unexpected\n", r->numcol() FATAL; | 
|  | } | 
|  | } | 
|  |  | 
|  | int multicol::print(int cv, int col) | 
|  | { | 
|  | if (col != 0) | 
|  | ERROR "multicolumn output must start in left column\n" FATAL; | 
|  | int curv = cv, maxv = cv;	// print left column | 
|  | for ( ; column[0].more(); column[0].advance()) { | 
|  | curv = column[0].current()->print(curv, 0); | 
|  | maxv = max(maxv, curv); | 
|  | } | 
|  | curv = cv;			// print right column | 
|  | for ( ; column[1].more(); column[1].advance()) { | 
|  | curv = column[1].current()->print(curv, 1); | 
|  | maxv = max(maxv, curv); | 
|  | } | 
|  | return maxv; | 
|  | } | 
|  |  | 
|  | void page::print() | 
|  | { | 
|  | static int tops = 1, bots = 1; | 
|  | if (!scratch.more()) { | 
|  | ERROR "## Here's what's left on squeue:\n" WARNING; | 
|  | squeue.dump(); | 
|  | ERROR "## Here's what's left on bfqueue:\n" WARNING; | 
|  | bfqueue.dump(); | 
|  | ERROR "## Here's what's left on ufqueue:\n" WARNING; | 
|  | ufqueue.dump(); | 
|  | ERROR "page %d appears to be empty\n", userpn WARNING; | 
|  | fflush(stderr), fflush(stdout), exit(0); | 
|  | // something is very wrong if this happens | 
|  | } | 
|  | printf("p%d\n", userpn);	// print troff output page number | 
|  | if (ptlist.more()) {		// print page header | 
|  | ptlist.current()->print(0, 0); | 
|  | ptlist.advance(); | 
|  | } else if (tops) { | 
|  | ERROR "ran out of page titles at %d\n", userpn WARNING; | 
|  | tops = 0; | 
|  | } | 
|  | int curv = 0; | 
|  | printf("V%d\n", curv = pagetop);// print page contents | 
|  | for ( ; scratch.more(); scratch.advance()) { | 
|  | curv = scratch.current()->print(curv, 0); | 
|  | } | 
|  | if (btlist.more()) {		// print page footer | 
|  | btlist.current()->print(0, 0); | 
|  | btlist.advance(); | 
|  | } else if (bots) { | 
|  | ERROR "ran out of page bottoms at %d\n", userpn WARNING; | 
|  | bots = 0; | 
|  | } | 
|  | printf("V%d\n", physbot);	// finish troff output page | 
|  | } | 
|  |  | 
|  | int	pagetop	= 0;		// top printing margin | 
|  | int	pagebot = 0;		// bottom printing margin | 
|  | int	physbot = 0;		// physical bottom of page | 
|  |  | 
|  | double minfull = 0.9;		// minimum fullness before padding | 
|  |  | 
|  | int	pn	= 0;		// cardinal page number | 
|  | int	userpn	= 0;		// page number derived from PT slugs | 
|  |  | 
|  | static void makepage() | 
|  | { | 
|  | page pg(pagebot - pagetop); | 
|  | ++pn; | 
|  | userpn = ptlist.more() ? ptlist.current()->pn() : pn; | 
|  | pg.fill(); | 
|  | pg.print(); | 
|  | } | 
|  |  | 
|  | static void conv(FILE *fp) | 
|  | { | 
|  | startup(fp);		// read slugs, etc. | 
|  | while (anymore()) | 
|  | makepage(); | 
|  | lastrange->print(0, 0);	// trailer | 
|  | checkout();		// check that everything was printed | 
|  | } | 
|  |  | 
|  | int | 
|  | main(int argc, char **argv) | 
|  | { | 
|  | static FILE *fp = stdin; | 
|  | progname = argv[0]; | 
|  | while (argc > 1 && argv[1][0] == '-') { | 
|  | switch (argv[1][1]) { | 
|  | case 'd': | 
|  | dbg = atoi(&argv[1][2]); | 
|  | if (dbg == 0) | 
|  | dbg = ~0; | 
|  | break; | 
|  | case 'm': | 
|  | minfull = 0.01*atof(&argv[1][2]); | 
|  | break; | 
|  | case 'c': | 
|  | coltol = 0.01*atof(&argv[1][2]); | 
|  | break; | 
|  | case 'w': | 
|  | wantwarn = 1; | 
|  | break; | 
|  | } | 
|  | argc--; | 
|  | argv++; | 
|  | } | 
|  | if (argc <= 1) | 
|  | conv(stdin); | 
|  | else | 
|  | while (--argc > 0) { | 
|  | if (strcmp(*++argv, "-") == 0) | 
|  | fp = stdin; | 
|  | else if ((fp = fopen(*argv, "r")) == NULL) | 
|  | ERROR "can't open %s\n", *argv FATAL; | 
|  | conv(fp); | 
|  | fclose(fp); | 
|  | } | 
|  | exit(0); | 
|  | return 0;	/* gcc */ | 
|  | } |