blob: 92d48db30ce25061018b9afe4a5c410fa0ed9d4e [file] [log] [blame]
#include "misc.h"
#include "slug.h"
#include <math.h>
static char *bufptr(int);
void slug::coalesce()
{
(this+1)->dp = dp; // pretty grimy, but meant to ensure
// that all output goes out.
// maybe it has to skip over PT's;
// some stuff is getting pushed inside PT..END
}
void slug::neutralize()
{
switch (type) {
case PAGE:
case UF:
case BF:
case PARM:
type = NEUTRAL;
coalesce();
break;
default:
ERROR "neutralized %d (%s) with %s\n",
type, typename(), headstr() WARNING;
break;
}
}
void slug::dump() // print contents of a slug
{
printf("# %d %-4.4s parm %d dv %d base %d s%d f%d H%d\n#\t\t%s\n",
serialno(), typename(), parm, dv, base,
size, font, hpos, headstr());
}
char *slug::headstr()
{
const int HEADLEN = 65;
static char buf[2*HEADLEN];
int j = 0;
char *s = bufptr(dp);
int n = (this+1)->dp - dp;
if (n >= HEADLEN)
n = HEADLEN;
for (int i = 0; i < n; i++)
switch (s[i]) {
case '\n':
case '\t':
case '\0':
case ' ':
break;
default:
buf[j++] = s[i];
break;
}
buf[j] = 0;
return buf;
}
static char *strindex(char s[], char t[]) // index of earliest t[] in s[]
{
for (int i = 0; s[i] != '\0'; i++) {
int j, k;
for (j = i, k = 0; t[k]!='\0' && s[j] == t[k]; j++, k++)
;
if (k > 0 && t[k] == '\0')
return s+i;
}
return 0;
}
void slug::slugout(int col)
{
static int numout = 0;
if (seen++)
ERROR "%s slug #%d seen %d times [%s]\n",
typename(), serialno(), seen, headstr() WARNING;
if (type == TM) {
char *p;
if ((p = strindex(bufptr(dp), "x X TM ")) != 0)
p += strlen("x X TM "); // skip junk
else
ERROR "strange TM [%s]\n", headstr() FATAL;
fprintf(stderr, "%d\t", userpn); // page # as prefix
for ( ; p < bufptr((this+1)->dp); p++)
putc(*p, stderr);
} else if (type == COORD) {
for (char *p = bufptr(dp); p < bufptr((this+1)->dp) && *p != '\n'; p++)
putc(*p, stdout);
printf(" # P %d X %d", userpn, hpos + col*offset);
return;
} else if (type == VBOX) {
if (numout++ > 0) // BUG??? might miss something
printf("s%d\nf%d\n", size, font);
printf("H%d\n", hpos + col*offset);
}
fwrite(bufptr(dp), sizeof(char), (this+1)->dp - dp, stdout);
}
char *slug::typename()
{
static char buf[50];
char *p = buf; // return value
switch(type) {
case EOF: p = "EOF"; break;
case VBOX: p = "VBOX"; break;
case SP: p = "SP"; break;
case BS: p = "BS"; break;
case US: p = "US"; break;
case BF: p = "BF"; break;
case UF: p = "UF"; break;
case PT: p = "PT"; break;
case BT: p = "BT"; break;
case END: p = "END"; break;
case NEUTRAL: p = "NEUT"; break;
case PAGE: p = "PAGE"; break;
case TM: p = "TM"; break;
case COORD: p = "COORD"; break;
case NE: p = "NE"; break;
case CMD: p = "CMD"; break;
case PARM: p = "PARM"; break;
default: sprintf(buf, "weird type %d", type);
}
return p;
}
// ================================================================================
// troff output-specific functions
// ================================================================================
const int DELTABUF = 500000; // grow the input buffer in chunks
static char *inbuf = 0; // raw text input collects here
static int ninbuf = 0; // byte count for inbuf
static char *inbp = 0; // next free slot in inbuf
int linenum = 0; // input line number
static inline void addc(int c) { *inbp++ = c; }
static void adds(char *s)
{
for (char *p = s; *p; p++)
addc(*p);
}
static int fullrune(char *c, int n)
{
if(n <= 0)
return 0;
if(n>=1 && (unsigned char)c[0] < 0x80)
return 1;
if(n>=2 && (unsigned char)c[0] < 0xE0)
return 1;
if(n>=3)
return 1;
return 0;
}
static char *getutf(FILE *fp) // get 1 utf-encoded char (might be multiple bytes)
{
static char buf[100];
char *p = buf;
for (*p = 0; (*p++ = getc(fp)) != EOF; ) {
*p = 0;
if (fullrune(buf, p-buf)) // found a valid character
break;
}
return buf;
}
static char *bufptr(int n) { return inbuf + n; } // scope of inbuf is too local
static inline int wherebuf() { return inbp - inbuf; }
static char *getstr(char *p, char *temp)
{ // copy next non-blank string from p to temp, update p
while (*p == ' ' || *p == '\t' || *p == '\n')
p++;
if (*p == '\0') {
temp[0] = 0;
return(NULL);
}
while (*p != ' ' && *p != '\t' && *p != '\n' && *p != '\0')
*temp++ = *p++;
*temp = '\0';
return(p);
}
/***************************************************************************
bounding box of a circular arc Eric Grosse 24 May 84
Conceptually, this routine generates a list consisting of the start,
end, and whichever north, east, south, and west points lie on the arc.
The bounding box is then the range of this list.
list = {start,end}
j = quadrant(start)
k = quadrant(end)
if( j==k && long way 'round ) append north,west,south,east
else
while( j != k )
append center+radius*[j-th of north,west,south,east unit vectors]
j += 1 (mod 4)
return( bounding box of list )
The following code implements this, with simple optimizations.
***********************************************************************/
static int quadrant(double x, double y)
{
if ( x>=0.0 && y> 0.0) return(1);
else if( x< 0.0 && y>=0.0) return(2);
else if( x<=0.0 && y< 0.0) return(3);
else if( x> 0.0 && y<=0.0) return(4);
else return 0; /* shut up lint */
}
static double xmin, ymin, xmax, ymax; // used by getDy
static void arc_extreme(double x0, double y0, double x1, double y1, double xc, double yc)
/* start, end, center */
{ /* assumes center isn't too far out */
double r;
int j, k;
printf("#start %g,%g, end %g,%g, ctr %g,%g\n", x0,y0, x1,y1, xc,yc);
y0 = -y0; y1 = -y1; yc = -yc; // troff's up is eric's down
x0 -= xc; y0 -= yc; /* move to center */
x1 -= xc; y1 -= yc;
xmin = (x0<x1)?x0:x1; ymin = (y0<y1)?y0:y1;
xmax = (x0>x1)?x0:x1; ymax = (y0>y1)?y0:y1;
r = sqrt(x0*x0 + y0*y0);
if (r > 0.0) {
j = quadrant(x0,y0);
k = quadrant(x1,y1);
if (j == k && y1*x0 < x1*y0) {
/* viewed as complex numbers, if Im(z1/z0)<0, arc is big */
if( xmin > -r) xmin = -r; if( ymin > -r) ymin = -r;
if( xmax < r) xmax = r; if( ymax < r) ymax = r;
} else {
while (j != k) {
switch (j) {
case 1: if( ymax < r) ymax = r; break; /* north */
case 2: if( xmin > -r) xmin = -r; break; /* west */
case 3: if( ymin > -r) ymin = -r; break; /* south */
case 4: if( xmax < r) xmax = r; break; /* east */
}
j = j%4 + 1;
}
}
}
xmin += xc; ymin += yc; ymin = -ymin;
xmax += xc; ymax += yc; ymax = -ymax;
}
static int getDy(char *p, int *dx, int *maxv)
// figure out where we are after a D'...'
{
int x, y, x1, y1; // for input values
char temp[50];
p++; // get to command letter
switch (*p++) {
case 'l': // line
sscanf(p, "%d %d", dx, &y);
return *maxv = y;
case 'a': // arc
sscanf(p, "%d %d %d %d", &x, &y, &x1, &y1);
*dx = x1 - x;
arc_extreme(0, 0, x+x1, y+y1, x, y); // sets [xy][max|min]
printf("#arc bounds x %g, %g; y %g, %g\n",
xmin, xmax, ymin, ymax);
*maxv = (int) (ymin+0.5);
return y + y1;
case '~': // spline
for (*dx = *maxv = y = 0; (p=getstr(p, temp)) != NULL; ) {
// above getstr() gets x value
*dx += atoi(temp);
p = getstr(p, temp); // this one gets y value
y += atoi(temp);
*maxv = max(*maxv, y); // ok???
if (*p == '\n' || *p == 0) // input is a single line;
break; // don't walk off end if realloc
}
return y;
case 'c': // circle, ellipse
sscanf(p, "%d", dx);
*maxv = *dx/2; // high water mark is ht/2
return 0;
case 'e':
sscanf(p, "%d %d", dx, &y);
*maxv = y/2; // high water mark is ht/2
return 0;
default: // weird stuff
return 0;
}
}
static int serialnum = 0;
slug eofslug()
{
slug ret;
ret.serialnum = serialnum;
ret.type = EOF;
ret.dp = wherebuf();
return ret;
}
slug getslug(FILE *fp)
{
if (inbuf == NULL) {
if ((inbuf = (char *) malloc(ninbuf = DELTABUF)) == NULL)
ERROR "no room for %d character input buffer\n", ninbuf FATAL;
inbp = inbuf;
}
if (wherebuf() > ninbuf-5000) {
// this is still flaky -- lines can be very long
int where = wherebuf(); // where we were
if ((inbuf = (char *) realloc(inbuf, ninbuf += DELTABUF)) == NULL)
ERROR "no room for %d character input buffer\n", ninbuf FATAL;
ERROR "grew input buffer to %d characters\n", ninbuf WARNING;
inbp = inbuf + where; // same offset in new array
}
static int baseV = 0; // first V command of preceding slug
static int curV = 0, curH = 0;
static int font = 0, size = 0;
static int baseadj = 0;
static int ncol = 1, offset = 0; // multi-column stuff
char str[1000], str2[1000], buf[3000], *p;
int firstV = 0, firstH = 0;
int maxV = curV;
int ocurV = curV, mxv = 0, dx = 0;
int sawD = 0; // > 0 if have seen D...
slug ret;
ret.serialnum = serialnum++;
ret.type = VBOX; // use the same as last by default
ret.dv = curV - baseV;
ret.hpos = curH;
ret.base = ret.parm = ret.parm2 = ret.seen = 0;
ret.font = font;
ret.size = size;
ret.dp = wherebuf();
ret.ncol = ncol;
ret.offset = offset;
ret.linenum = linenum; // might be low
for (;;) {
int c, m, n; // for input values
int sign; // hoisted from case 'h' below
switch (c = getc(fp)) {
case EOF:
ret.type = EOF;
ret.dv = 0;
if (baseadj)
printf("# adjusted %d bases\n", baseadj);
printf("# %d characters, %d lines\n", wherebuf(), linenum);
return ret;
case 'V':
fscanf(fp, "%d", &n);
if (firstV++ == 0) {
ret.dv = n - baseV;
baseV = n;
} else {
sprintf(buf, "v%d", n - curV);
adds(buf);
}
curV = n;
maxV = max(maxV, curV);
break;
case 'H': // absolute H motion
fscanf(fp, "%d", &n);
if (firstH++ == 0) {
ret.hpos = n;
} else {
sprintf(buf, "h%d", n - curH);
adds(buf);
}
curH = n;
break;
case 'h': // relative H motion
addc(c);
sign = 1;
if ((c = getc(fp)) == '-') {
addc(c);
sign = -1;
c = getc(fp);
}
for (n = 0; isdigit(c); c = getc(fp)) {
addc(c);
n = 10 * n + c - '0';
}
curH += n * sign;
ungetc(c, fp);
break;
case 'x': // device control: x ...
addc(c);
fgets(buf, (int) sizeof(buf), fp);
linenum++;
adds(buf);
if (buf[0] == ' ' && buf[1] == 'X') { // x X ...
if (2 != sscanf(buf+2, "%s %d", str, &n))
n = 0;
if (eq(str, "SP")) { // X SP n
ret.type = SP; // paddable SPace
ret.dv = n; // of height n
} else if (eq(str, "BS")) {
ret.type = BS; // Breakable Stream
ret.parm = n; // >=n VBOXES on a page
} else if (eq(str, "BF")) {
ret.type = BF; // Breakable Float
ret.parm = ret.parm2 = n;
// n = pref center (as UF)
} else if (eq(str, "US")) {
ret.type = US; // Unbreakable Stream
ret.parm = n;
} else if (eq(str, "UF")) {
ret.type = UF; // Unbreakable Float
ret.parm = ret.parm2 = n;
// n = preferred center
// to select several,
// use several UF lines
} else if (eq(str, "PT")) {
ret.type = PT; // Page Title
ret.parm = n;
} else if (eq(str, "BT")) {
ret.type = BT; // Bottom Title
ret.parm = n;
} else if (eq(str, "END")) {
ret.type = END;
ret.parm = n;
} else if (eq(str, "TM")) {
ret.type = TM; // Terminal Message
ret.dv = 0;
} else if (eq(str, "COORD")) {
ret.type = COORD;// page COORDinates
ret.dv = 0;
} else if (eq(str, "NE")) {
ret.type = NE; // NEed to break page
ret.dv = n; // if <n units left
} else if (eq(str, "MC")) {
ret.type = MC; // Multiple Columns
sscanf(buf+2, "%s %d %d",
str, &ncol, &offset);
ret.ncol = ncol;
ret.offset = offset;
} else if (eq(str, "CMD")) {
ret.type = CMD; // CoMmaNd
sscanf(buf+2, "%s %s", str2, str);
if (eq(str, "FC")) // Freeze 2-Col
ret.parm = FC;
else if (eq(str, "FL")) // FLush
ret.parm = FL;
else if (eq(str, "BP")) // Break Page
ret.parm = BP;
else ERROR "unknown command %s\n",
str WARNING;
} else if (eq(str, "PARM")) {
ret.type = PARM;// PARaMeter
sscanf(buf+2, "%s %s %d", str2, str, &ret.parm2);
if (eq(str, "NP")) // New Page
ret.parm = NP;
else if (eq(str, "FO")) // FOoter
ret.parm = FO;
else if (eq(str, "PL")) // Page Length
ret.parm = PL;
else if (eq(str, "MF")) // MinFull
ret.parm = MF;
else if (eq(str, "CT")) // ColTol
ret.parm = CT;
else if (eq(str, "WARN")) //WARNings?
ret.parm = WARN;
else if (eq(str, "DBG"))// DeBuG
ret.parm = DBG;
else ERROR "unknown parameter %s\n",
str WARNING;
} else
break; // out of switch
if (firstV > 0)
ERROR "weird x X %s in mid-VBOX\n",
str WARNING;
return ret;
}
break;
case 'n': // end of line
fscanf(fp, "%d %d", &n, &m);
ret.ht = n;
ret.base = m;
getc(fp); // newline
linenum++;
sprintf(buf, "n%d %d\n", ret.ht, ret.base);
adds(buf);
if (!firstV++)
baseV = curV;
// older incarnations of this program used ret.base
// in complicated and unreliable ways;
// example: if ret.ht + ret.base < ret.dv, ret.base = 0
// this was meant to avoid double-counting the space
// around displayed equations; it didn't work
// Now, we believe ret.base = 0, otherwise we give it
// a value we have computed.
if (ret.base == 0 && sawD == 0)
return ret; // don't fiddle 0-bases
if (ret.base != maxV - baseV) {
ret.base = maxV - baseV;
baseadj++;
}
if (ret.type != VBOX)
ERROR "%s slug (type %d) has base = %d\n",
ret.typename(), ret.type, ret.base WARNING;
return ret;
case 'p': // new page
fscanf(fp, "%d", &n);
ret.type = PAGE;
curV = baseV = ret.dv = 0;
ret.parm = n; // just in case someone needs it
return ret;
case 's': // size change snnn
fscanf(fp, "%d", &size);
sprintf(buf, "s%d\n", size);
adds(buf);
break;
case 'f': // font fnnn
fscanf(fp, "%d", &font);
sprintf(buf, "f%d\n", font);
adds(buf);
break;
case '\n':
linenum++;
/* fall through */
case ' ':
addc(c);
break;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
// two motion digits plus a character
addc(c);
n = c - '0';
addc(c = getc(fp));
curH += 10 * n + c - '0';
adds(getutf(fp));
if (!firstV++)
baseV = curV;
break;
case 'c': // single ascii character
addc(c);
adds(getutf(fp));
if (!firstV++)
baseV = curV;
break;
case 'C': // Cxyz\n
case 'N': // Nnnn\n
addc(c);
while ((c = getc(fp)) != ' ' && c != '\n')
addc(c);
addc(c);
if (!firstV++)
baseV = curV;
linenum++;
break;
case 'D': // draw function: D.*\n
sawD++;
p = bufptr(wherebuf()); // where does the D start
addc(c);
while ((c = getc(fp)) != '\n')
addc(c);
addc(c);
if (!firstV++)
baseV = curV;
ocurV = curV, mxv = 0, dx = 0;
curV += getDy(p, &dx, &mxv); // figure out how big it is
maxV = max(max(maxV, curV), ocurV+mxv);
curH += dx;
linenum++;
break;
case 'v': // relative vertical vnnn
addc(c);
if (!firstV++)
baseV = curV;
sign = 1;
if ((c = getc(fp)) == '-') {
addc(c);
sign = -1;
c = getc(fp);
}
for (n = 0; isdigit(c); c = getc(fp)) {
addc(c);
n = 10 * n + c - '0';
}
ungetc(c, fp);
curV += n * sign;
maxV = max(maxV, curV);
addc('\n');
break;
case 'w': // word space
addc(c);
break;
case '#': // comment
addc(c);
while ((c = getc(fp)) != '\n')
addc(c);
addc('\n');
linenum++;
break;
default:
ERROR "unknown input character %o %c (%50.50s)\n",
c, c, bufptr(wherebuf()-50) WARNING;
abort();
break;
}
}
}