plan9port

[fork] Plan 9 from user space
git clone git://src.adamsgaard.dk/plan9port # fast
git clone https://src.adamsgaard.dk/plan9port.git # slow
Log | Files | Refs | README | LICENSE Back to index

page.cc (17831B)


      1 #include	"misc.h"
      2 #include	"slug.h"
      3 #include	"range.h"
      4 #include	"page.h"
      5 
      6 const int	MAXRANGES	= 1000;
      7 static range *ptemp[MAXRANGES];		// for movefloats()
      8 
      9 static void swapright(int n)		// used by movefloats()
     10 {
     11 	range *t = ptemp[n];
     12 	ptemp[n] = ptemp[n+1];
     13 	ptemp[n+1] = t;
     14 	ptemp[n]->setaccum( ptemp[n+1]->accum() -
     15 			    ptemp[n+1]->rawht() + ptemp[n]->rawht() );
     16 	ptemp[n+1]->setaccum( ptemp[n]->accum() + ptemp[n+1]->rawht() );
     17 }
     18 
     19 // Figure out the goal position for each floating range on scratch,
     20 // and move it past stream ranges until it's as close to its goal as possible.
     21 static void movefloats(stream *scratch, double scale)
     22 {
     23 	int nranges, i;
     24 	const int Huge = 100000;
     25 
     26 	for (nranges = 0; scratch->more(); scratch->advance())
     27 		ptemp[nranges++] = scratch->current();
     28 	scratch->freeall();
     29 	ufrange rtemp;
     30 	ptemp[nranges] = &rtemp;
     31 	rtemp.setgoal(Huge);
     32 	int accumV = 0;				// compute accum values and
     33 	for (i = 0; i < nranges; i++) {		// pick closest goal for floats
     34 		ptemp[i]->pickgoal(accumV, scale);
     35 		ptemp[i]->setaccum(accumV += ptemp[i]->rawht());
     36 	}
     37 	int j;					// index for inner loop below:
     38 	for (i = nranges; --i >= 0; )		// stably sort floats to bottom
     39 		for (j = i; j < nranges; j++)
     40 			if (ptemp[j]->goal() > ptemp[j+1]->goal())
     41 				swapright(j);
     42 			else
     43 				break;
     44 	if (dbg & 16)
     45 		printf("#movefloats:  before floating, from bottom:\n");
     46 	for (i = nranges; --i >= 0; ) {		// find topmost float
     47 		if (ptemp[i]->goal() == NOGOAL)
     48 			break;
     49 		if (dbg & 16)
     50 			printf("# serialno %d goal %d height %d\n",
     51 				ptemp[i]->serialno(), ptemp[i]->goal(),
     52 				ptemp[i]->rawht());
     53 	}					// i+1 is topmost float
     54 	for (i++ ; i < nranges; i++)		// move each float up the page
     55 		for (j = i; j > 0; j--)		// as long as closer to its goal
     56 			if (ptemp[j]->goal()
     57 			  <= ptemp[j-1]->accum() + ptemp[j]->rawht()/2
     58 			  && ptemp[j-1]->goal() == NOGOAL)
     59 				swapright(j-1);
     60 			else
     61 				break;
     62 	if (ptemp[nranges] != &rtemp)
     63 		ERROR "goal sentinel has disappeared from movefloats" FATAL;
     64 	for (i = 0; i < nranges; i++)		// copy sorted list back
     65 		scratch->append(ptemp[i]);
     66 }
     67 
     68 // Traverse the leaves of a tree of ranges, filtering out only SP and VBOX.
     69 static range *filter(generator *g)
     70 {
     71 	range *r;
     72 	while ((r = g->next()) != 0)
     73 		if (r->isvbox() || r->issp())
     74 			break;
     75 	return r;
     76 }
     77 
     78 // Zero out leading and trailing spaces; coalesce adjacent SP's.
     79 static void trimspace(stream *scratch)
     80 {
     81 	generator g;
     82 	range *r, *prevr = 0;
     83 
     84 	for (g = scratch; (r = filter(&g)) != 0 && r->issp(); prevr = r)
     85 		r->setheight(0);		// zap leading SP
     86 	for ( ; (r = filter(&g)) != 0; prevr = r)
     87 		if (r->issp())
     88 			if (prevr && prevr->issp()) {
     89 						// coalesce adjacent SPs
     90 				r->setheight(max(r->rawht(), prevr->height()));
     91 				prevr->setheight(0);
     92 			} else			// a VBOX intervened
     93 				r->setheight(r->rawht());
     94 	if (prevr && prevr->issp())		// zap *all* trailing space
     95 		prevr->setheight(0);		// (since it all coalesced
     96 						// into the last one)
     97 }
     98 
     99 // Pad the non-zero SP's in scratch so the total height is wantht.
    100 // Note that the SP values in scratch are not the raw values, and
    101 // indeed may already have been padded.
    102 static void justify(stream *scratch, int wantht)
    103 {
    104 	range *r;
    105 	int nsp = 0, hsp = 0;
    106 
    107 	int adjht = scratch->height();
    108 					// Find all the spaces.
    109 	generator g;
    110 	for (g = scratch; (r = g.next()) != 0; )
    111 		if (r->issp() && r->height() > 0) {
    112 			nsp++;
    113 			hsp += r->height();
    114 		}
    115 	int excess = wantht - adjht;
    116 	if (excess < 0)
    117 		ERROR "something on page %d is oversize by %d\n",
    118 			userpn, -excess WARNING;
    119 	if (dbg & 16)
    120 		printf("# justify %d: excess %d nsp %d hsp %d adjht %d\n",
    121 			userpn, excess, nsp, hsp, adjht);
    122 	if (excess <= 0 || nsp == 0)
    123 		return;
    124 					// Redistribute the excess space.
    125 	for (g = scratch; (r = g.next()) != 0; )
    126 		if (r->issp() && r->height() > 0) {
    127 			int delta = (int) ((float)(r->height()*excess)/hsp + 0.5);
    128 			if (dbg & 16)
    129 				printf("# pad space %d by %d: hsp %d excess %d\n",
    130 					r->height(), delta, hsp, excess);
    131 			r->setheight(r->height() + delta);
    132 		}
    133 }
    134 
    135 // If r were added to s, would the height of the composed result be at most maxht?
    136 int wouldfit(range *r, stream *s, int maxht)
    137 {
    138 	if (r->rawht() + s->rawht() <= maxht)
    139 		return 1;		// the conservative test succeeded
    140 	stream scratch;			// local playground for costly test
    141 	for (stream cd = *s; cd.more(); cd.advance())
    142 		scratch.append(cd.current());
    143 	scratch.append(r);
    144 	movefloats(&scratch, ((double) scratch.rawht())/maxht);
    145 	trimspace(&scratch);
    146 	int retval = scratch.height() <= maxht;
    147 	scratch.freeall();
    148 	return retval;
    149 }
    150 
    151 // If s1 were added to s, would the height of the composed result be at most maxht?
    152 // The computational structure is similar to that above.
    153 int wouldfit(stream *s1, stream *s, int maxht)
    154 {
    155 	if (s1->rawht() + s->rawht() <= maxht)
    156 		return 1;
    157 	stream scratch, cd;
    158 	for (cd = *s; cd.more(); cd.advance())
    159 		scratch.append(cd.current());
    160 	for (cd = *s1; cd.more(); cd.advance())
    161 		scratch.append(cd.current());
    162 	movefloats(&scratch, ((double) scratch.rawht())/maxht);
    163 	trimspace(&scratch);
    164 	int retval = scratch.height() <= maxht;
    165 	scratch.freeall();
    166 	return retval;
    167 }
    168 
    169 // All of stream *s is destined for one column or the other; which is it to be?
    170 void multicol::choosecol(stream *s, int goalht)
    171 {
    172 	stream *dest;
    173 	if (!leftblocked && wouldfit(s, &(column[0]), goalht))
    174 		dest = &(column[0]);
    175 	else {
    176 		dest = &(column[1]);
    177 		if (!s->current()->floatable())
    178 					// a stream item is going into the right
    179 					// column, so no more can go into the left.
    180 			leftblocked = 1;
    181 	}
    182 	for (stream cd = *s; cd.more(); cd.advance())
    183 		dest->append(cd.current());
    184 }
    185 
    186 double coltol = 0.5;
    187 
    188 // Try, very hard, to put everything in the multicol into two columns
    189 // so that the total height is at most htavail.
    190 void multicol::compose(int defonly)
    191 {
    192 	if (!nonempty()) {
    193 		setheight(0);
    194 		return;
    195 	}
    196 	scratch.freeall();	// fill scratch with everything destined
    197 				// for either column
    198 	stream cd;
    199 	for (cd = definite; cd.more(); cd.advance())
    200 		scratch.append(cd.current());
    201 	if (!defonly)
    202 		for (cd = *(currpage->stage); cd.more(); cd.advance())
    203 			if (cd.current()->numcol() == 2)
    204 				scratch.append(cd.current());
    205 	scratch.restoreall();		// in particular, floatables' goals
    206 	int i;
    207 	int rawht = scratch.rawht();
    208 	int halfheight = (int)(coltol*rawht);
    209 					// choose a goal height
    210 	int maxht = defonly ? halfheight : htavail;
    211 secondtry:
    212 	for (i = 0; i < 2; i++)
    213 		column[i].freeall();
    214 	leftblocked = 0;
    215 	cd = scratch;
    216 	while (cd.more()) {
    217 		queue ministage;	// for the minimally acceptable chunks
    218 		ministage.freeall();	// that are to be added to either column
    219 		while (cd.more() && !cd.current()->issentinel()) {
    220 			ministage.enqueue(cd.current());
    221 			cd.advance();
    222 		}
    223 		choosecol(&ministage, maxht);
    224 		if (cd.more() && cd.current()->issentinel())
    225 			cd.advance();	// past sentinel
    226 	}
    227 	if (height() > htavail && maxht != htavail) {
    228 					// We tried to balance the columns, but
    229 					// the result was too tall.  Go back
    230 					// and try again with the less ambitious
    231 					// goal of fitting the space available.
    232 		maxht = htavail;
    233 		goto secondtry;
    234 	}
    235 	for (i = 0; i < 2; i++) {
    236 		movefloats(&(column[i]), ((double) column[i].rawht())/currpage->pagesize);
    237 		trimspace(&(column[i]));
    238 	}
    239 	if (dbg & 32) {
    240 		printf("#multicol::compose: htavail %d maxht %d dv %d\n",
    241 			htavail, maxht, height());
    242 		dump();
    243 	}
    244 	if (defonly)
    245 		stretch(height());
    246 }
    247 
    248 // A sequence of two-column ranges waits on the stage.
    249 // So long as the page's skeleton hasn't changed--that is, the maximum height
    250 // available to the two-column chunk is the same--we just use the columns that
    251 // have been built up so far, and choose a column into which to put the stage.
    252 // If the skeleton has changed, however, then we may need to make entirely
    253 // new decisions about which column gets what, so we recompose the whole page.
    254 void multicol::tryout()
    255 {
    256 	if (htavail == prevhtavail)
    257 		choosecol(currpage->stage, htavail);
    258 	else
    259 		currpage->compose(DRAFT);
    260 	prevhtavail = htavail;
    261 }
    262 
    263 // Make both columns the same height.
    264 // (Maybe this should also be governed by minfull,
    265 // to prevent padding very underfull columns.)
    266 void multicol::stretch(int wantht)
    267 {
    268 	if (wantht < height())
    269 		ERROR "page %d: two-column chunk cannot shrink\n", userpn FATAL;
    270 	for (int i = 0; i < 2; i++)
    271 		justify(&(column[i]), wantht);
    272 	if (dbg & 16)
    273 		printf("#col hts: left %d right %d\n",
    274 			column[0].height(), column[1].height());
    275 }
    276 
    277 // Report an upper bound on how tall the current two-column object is.
    278 // The (possibly composed) heights of the two columns give a crude upper
    279 // bound on the total height.  If the result is more than the height
    280 // available for the two-column object, then the columns are each
    281 // composed to give a better estimate of their heights.
    282 int multicol::height()
    283 {
    284 	int retval = max(column[0].height(), column[1].height());
    285 	if (retval < htavail)
    286 		return retval;
    287 	for (int i = 0; i < 2; i++) {
    288 		movefloats(&(column[i]), ((double) column[i].height())/currpage->pagesize);
    289 		trimspace(&(column[i]));
    290 	}
    291 	return max(column[0].height(), column[1].height());
    292 }
    293 
    294 void multicol::dump()
    295 {
    296 	printf("####2COL dv %d\n", height());
    297 	printf("# left column:\n");
    298 	column[0].dump();
    299 	printf("# right column:\n");
    300 	column[1].dump();
    301 }
    302 
    303 // From the head of queue qp, peel off a piece whose raw height is at most space.
    304 int peeloff(stream *qp, int space)
    305 {
    306 	stream *s1 = qp->current()->children();
    307 	if (!(s1 && s1->more() && s1->current()->height() <= space))
    308 					// in other words, either qp's head is
    309 					// not nested, or its first subrange
    310 		return 0;		// is also too big, so we give up
    311 	qp->split();
    312 	s1 = qp->current()->children();
    313 	stream *s2 = qp->next()->children();
    314 	while (s2->more() && s2->current()->rawht() <= space) {
    315 		s1->append(s2->current());
    316 		space -= s2->current()->rawht();
    317 		s2->advance();
    318 	}
    319 	return 1;
    320 }
    321 
    322 // There are four possibilities for consecutive calls to tryout().
    323 // If we're processing a sequence of single-column ranges, tryout()
    324 // uses the original algorithm: (1) conservative test; (2) costly test;
    325 // (3) split a breakable item.
    326 // If we're processing a sequence of double-column ranges, tryout()
    327 // defers to twocol->tryout(), which gradually builds up the contents
    328 // of the two columns until they're as tall as they can be without
    329 // exceeding twocol->htavail.
    330 // If we're processing a sequence of single-column ranges and we
    331 // get a double-column range, then we use compose() to build a
    332 // skeleton page and set twocol->htavail, the maximum height that
    333 // should be occupied by twocol.
    334 // If we're processing a sequence of double-column ranges and we
    335 // get a single-column range, then we should go back and squish
    336 // the double-column chunk as short as possible before we see if
    337 // we can fit the single-column range.
    338 void page::tryout()
    339 {
    340 	if (!stage->more())
    341 		ERROR "empty stage in page::tryout()\n" FATAL;
    342 	int curnumcol = stage->current()->numcol();
    343 	if (dbg & 32) {
    344 		printf("#page::tryout(): ncol = %d, prevncol = %d; on stage:\n",
    345 			curnumcol, prevncol);
    346 		stage->dump();
    347 		printf("#END of stage contents\n");
    348 	}
    349 	switch(curnumcol) {
    350 	default:
    351 		ERROR "unexpected number of columns in tryout(): %d\n",
    352 			stage->current()->numcol() FATAL;
    353 		break;
    354 	case 1:
    355 		if (prevncol == 2)
    356 			compose(FINAL);
    357 		if (wouldfit(stage, &definite, pagesize - twocol->height()))
    358 			commit();
    359 		else if (stage->current()->breakable() || blank()
    360 			&& peeloff(stage,
    361 				pagesize - (definite.height() + twocol->height()))) {
    362 			// first add the peeled-off part that fits
    363 			adddef(stage->dequeue());
    364 			// then send the rest back for later
    365 			stage->current()->setbreaking();
    366 			welsh();
    367 		} else if (blank()) {
    368 			stage->current()->rdump();
    369 			ERROR "A %s is too big to continue.\n",
    370 			stage->current()->typename() FATAL;
    371 		} else
    372 			welsh();
    373 		break;
    374 	case 2:
    375 		if (prevncol == 1)
    376 			compose(DRAFT);
    377 		else
    378 			twocol->tryout();
    379 		if (scratch.height() <= pagesize)
    380 			commit();
    381 		else
    382 			welsh();
    383 		break;
    384 	}
    385 	prevncol = curnumcol;
    386 }
    387 
    388 // To compose the page, we (1) fill scratch with the stuff that's meant to
    389 // go on the page; (2) compose scratch as best we can; (3) set the maximum
    390 // height available to the two-column part of the page; (4) have the two-
    391 // column part compose itself.
    392 // In the computation of twocol->htavail, it does not matter that
    393 // twocol->height() is merely an upper bound, because it is merely being
    394 // subtracted out to give the exact total height of the single-column stuff.
    395 void page::compose(int final)
    396 {
    397 	makescratch(final);
    398 	int adjht = scratch.rawht();
    399 	if (dbg & 16)
    400 		printf("# page %d measure %d\n", userpn, adjht);
    401 	movefloats(&scratch, ((double) adjht)/pagesize);
    402 	trimspace(&scratch);
    403 	twocol->htavail = pagesize - (scratch.height() - twocol->height());
    404 	twocol->compose(final);
    405 	adjht = scratch.height();
    406 	if (dbg & 16)
    407 		printf("# page %d measure %d after trim\n", userpn, adjht);
    408 }
    409 
    410 // Fill the scratch area with ranges destined for the page.
    411 // If defonly == 0, then add anything that's on stage--this is a trial run.
    412 // If defonly != 0, use only what's definitely on the page.
    413 void page::makescratch(int defonly)
    414 {
    415 	scratch.freeall();
    416 	stream cd;
    417 	for (cd = definite; cd.more(); cd.advance())
    418 		scratch.append(cd.current());
    419 	if (!defonly)
    420 		for (cd = *stage; cd.more(); cd.advance())
    421 			if (cd.current()->numcol() == 1)
    422 				scratch.append(cd.current());
    423 	if (twocol->nonempty())
    424 		scratch.append(twocol);
    425 }
    426 
    427 // Accept the current contents of the stage.
    428 // If the stage contains two-column ranges, add a sentinel to indicate the end
    429 // of a chunk of stage contents.
    430 void page::commit()
    431 {
    432 	if (dbg & 4)
    433 		printf("#entering page::commit()\n");
    434 	int numcol = 0;
    435 	while (stage->more()) {
    436 		numcol = stage->current()->numcol();
    437 		adddef(stage->dequeue());
    438 	}
    439 	if (numcol == 2)
    440 		adddef(new sentrange);
    441 }
    442 
    443 // Send the current contents of the stage back to its source.
    444 void page::welsh()
    445 {
    446 	if (dbg & 4)
    447 		printf("#entering page::welsh()\n");
    448 	while (stage->more()) {
    449 		range *r = stage->dequeue();
    450 		r->enqueue(ANDBLOCK);
    451 	}
    452 }
    453 
    454 enum { USonly = 1 };
    455 
    456 // So long as anything is eligible to go onto the page, keep trying.
    457 // Once nothing is eligible, compose and justify the page.
    458 void page::fill()
    459 {
    460 	while (stage->prime())
    461 		stage->pend();
    462 	compose(FINAL);
    463 	if (dbg & 16)
    464 		scratch.dump();
    465 	if (anymore()) {
    466 		int adjht = scratch.height();
    467 		if (adjht > minfull*pagesize) {
    468 			justify(&scratch, pagesize);
    469 			adjht = scratch.height();
    470 			int stretchamt = max(pagesize - adjht, 0);
    471 			twocol->stretch(twocol->height() + stretchamt);
    472 					// in case the page's stretchability lies
    473 					// entirely in its two-column part
    474 		} else
    475 			ERROR "page %d only %.0f%% full; will not be adjusted\n",
    476 				userpn, 100*(double) adjht/pagesize WARNING;
    477 	}
    478 }
    479 
    480 void page::adddef(range *r)
    481 {
    482 	if (dbg & 4)
    483 		printf("#entering page::adddef()\n");
    484 	switch (r->numcol()) {
    485 	case 1:	definite.append(r);
    486 		break;
    487 	case 2: twocol->definite.append(r);
    488 		break;
    489 	default: ERROR "%d-column range unexpected\n", r->numcol() FATAL;
    490 	}
    491 }
    492 
    493 int multicol::print(int cv, int col)
    494 {
    495 	if (col != 0)
    496 		ERROR "multicolumn output must start in left column\n" FATAL;
    497 	int curv = cv, maxv = cv;	// print left column
    498 	for ( ; column[0].more(); column[0].advance()) {
    499 		curv = column[0].current()->print(curv, 0);
    500 		maxv = max(maxv, curv);
    501 	}
    502 	curv = cv;			// print right column
    503 	for ( ; column[1].more(); column[1].advance()) {
    504 		curv = column[1].current()->print(curv, 1);
    505 		maxv = max(maxv, curv);
    506 	}
    507 	return maxv;
    508 }
    509 
    510 void page::print()
    511 {
    512 	static int tops = 1, bots = 1;
    513 	if (!scratch.more()) {
    514 		ERROR "## Here's what's left on squeue:\n" WARNING;
    515 		squeue.dump();
    516 		ERROR "## Here's what's left on bfqueue:\n" WARNING;
    517 		bfqueue.dump();
    518 		ERROR "## Here's what's left on ufqueue:\n" WARNING;
    519 		ufqueue.dump();
    520 		ERROR "page %d appears to be empty\n", userpn WARNING;
    521 		fflush(stderr), fflush(stdout), exit(0);
    522 					// something is very wrong if this happens
    523 	}
    524 	printf("p%d\n", userpn);	// print troff output page number
    525 	if (ptlist.more()) {		// print page header
    526 		ptlist.current()->print(0, 0);
    527 		ptlist.advance();
    528 	} else if (tops) {
    529 		ERROR "ran out of page titles at %d\n", userpn WARNING;
    530 		tops = 0;
    531 	}
    532 	int curv = 0;
    533 	printf("V%d\n", curv = pagetop);// print page contents
    534 	for ( ; scratch.more(); scratch.advance()) {
    535 		curv = scratch.current()->print(curv, 0);
    536 	}
    537 	if (btlist.more()) {		// print page footer
    538 		btlist.current()->print(0, 0);
    539 		btlist.advance();
    540 	} else if (bots) {
    541 		ERROR "ran out of page bottoms at %d\n", userpn WARNING;
    542 		bots = 0;
    543 	}
    544 	printf("V%d\n", physbot);	// finish troff output page
    545 }
    546 
    547 int	pagetop	= 0;		// top printing margin
    548 int	pagebot = 0;		// bottom printing margin
    549 int	physbot = 0;		// physical bottom of page
    550 
    551 double minfull = 0.9;		// minimum fullness before padding
    552 
    553 int	pn	= 0;		// cardinal page number
    554 int	userpn	= 0;		// page number derived from PT slugs
    555 
    556 static void makepage()
    557 {
    558 	page pg(pagebot - pagetop);
    559 	++pn;
    560 	userpn = ptlist.more() ? ptlist.current()->pn() : pn;
    561 	pg.fill();
    562 	pg.print();
    563 }
    564 
    565 static void conv(FILE *fp)
    566 {
    567 	startup(fp);		// read slugs, etc.
    568 	while (anymore())
    569 		makepage();
    570 	lastrange->print(0, 0);	// trailer
    571 	checkout();		// check that everything was printed
    572 }
    573 
    574 int
    575 main(int argc, char **argv)
    576 {
    577 	static FILE *fp = stdin;
    578 	progname = argv[0];
    579 	while (argc > 1 && argv[1][0] == '-') {
    580 		switch (argv[1][1]) {
    581 		case 'd':
    582 			dbg = atoi(&argv[1][2]);
    583 			if (dbg == 0)
    584 				dbg = ~0;
    585 			break;
    586 		case 'm':
    587 			minfull = 0.01*atof(&argv[1][2]);
    588 			break;
    589 		case 'c':
    590 			coltol = 0.01*atof(&argv[1][2]);
    591 			break;
    592 		case 'w':
    593 			wantwarn = 1;
    594 			break;
    595 		}
    596 		argc--;
    597 		argv++;
    598 	}
    599 	if (argc <= 1)
    600 		conv(stdin);
    601 	else
    602 		while (--argc > 0) {
    603 			if (strcmp(*++argv, "-") == 0)
    604 				fp = stdin;
    605 			else if ((fp = fopen(*argv, "r")) == NULL)
    606 				ERROR "can't open %s\n", *argv FATAL;
    607 			conv(fp);
    608 			fclose(fp);
    609 		}
    610 	exit(0);
    611 	return 0;	/* gcc */
    612 }