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