mirror of
https://github.com/9fans/plan9port.git
synced 2025-01-15 11:20:03 +00:00
603 lines
15 KiB
C++
603 lines
15 KiB
C++
#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;
|
|
}
|
|
}
|
|
}
|