#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() { int h; stream s = *this; for (h = 0; s.more(); s.advance()) h += s.current()->height(); return h; } int stream::rawht() { int h; stream s = *this; for (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()) != 0) 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) { slug *p; for (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 = 0; // 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 *p, *prevV = 0; for (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(); } }