Checkpoint: pull in mpm; merge pic from Taj's version of the world
diff --git a/src/cmd/mpm/slug.cc b/src/cmd/mpm/slug.cc
new file mode 100644
index 0000000..14378b9
--- /dev/null
+++ b/src/cmd/mpm/slug.cc
@@ -0,0 +1,603 @@
+#include	"misc.h"
+#include	"slug.h"
+//#include	<libc.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 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 (mblen(buf, sizeof buf) > 0)	// 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;
+		}
+	}
+}