Blob


1 #include "misc.h"
2 #include "slug.h"
3 #include "range.h"
4 #include "page.h"
6 const int MAXRANGES = 1000;
7 static range *ptemp[MAXRANGES]; // for movefloats()
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 }
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;
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 }
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 }
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;
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 }
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)
104 range *r;
105 int nsp = 0, hsp = 0;
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();
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);
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)
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;
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)
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;
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)
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;
182 for (stream cd = *s; cd.more(); cd.advance())
183 dest->append(cd.current());
186 double coltol = 0.5;
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)
192 if (!nonempty()) {
193 setheight(0);
194 return;
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();
223 choosecol(&ministage, maxht);
224 if (cd.more() && cd.current()->issentinel())
225 cd.advance(); // past sentinel
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;
235 for (i = 0; i < 2; i++) {
236 movefloats(&(column[i]), ((double) column[i].rawht())/currpage->pagesize);
237 trimspace(&(column[i]));
239 if (dbg & 32) {
240 printf("#multicol::compose: htavail %d maxht %d dv %d\n",
241 htavail, maxht, height());
242 dump();
244 if (defonly)
245 stretch(height());
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()
256 if (htavail == prevhtavail)
257 choosecol(currpage->stage, htavail);
258 else
259 currpage->compose(DRAFT);
260 prevhtavail = htavail;
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)
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());
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()
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]));
291 return max(column[0].height(), column[1].height());
294 void multicol::dump()
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();
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)
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();
319 return 1;
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()
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");
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;
385 prevncol = curnumcol;
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)
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);
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)
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);
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()
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());
439 if (numcol == 2)
440 adddef(new sentrange);
443 // Send the current contents of the stage back to its source.
444 void page::welsh()
446 if (dbg & 4)
447 printf("#entering page::welsh()\n");
448 while (stage->more()) {
449 range *r = stage->dequeue();
450 r->enqueue(ANDBLOCK);
454 enum { USonly = 1 };
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()
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;
480 void page::adddef(range *r)
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;
493 int multicol::print(int cv, int col)
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);
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);
507 return maxv;
510 void page::print()
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
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;
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);
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;
544 printf("V%d\n", physbot); // finish troff output page
547 int pagetop = 0; // top printing margin
548 int pagebot = 0; // bottom printing margin
549 int physbot = 0; // physical bottom of page
551 double minfull = 0.9; // minimum fullness before padding
553 int pn = 0; // cardinal page number
554 int userpn = 0; // page number derived from PT slugs
556 static void makepage()
558 page pg(pagebot - pagetop);
559 ++pn;
560 userpn = ptlist.more() ? ptlist.current()->pn() : pn;
561 pg.fill();
562 pg.print();
565 static void conv(FILE *fp)
567 startup(fp); // read slugs, etc.
568 while (anymore())
569 makepage();
570 lastrange->print(0, 0); // trailer
571 checkout(); // check that everything was printed
574 int
575 main(int argc, char **argv)
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;
596 argc--;
597 argv++;
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);
610 exit(0);
611 return 0; /* gcc */