blob: 829cb8c46d46e73106fd8974c25db0047a1c8249 [file] [log] [blame]
#include <math.h>
#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();
}
}