#include <ascii.h>
#include <xfbuf.h>

char *next_arg();
char *skip_delim();
char *strip_path();

/* 
	TF:	Text formatter
*/

#define SIGNON "Text Formatter  Copyright Tom Jennings 7 July 88"

#define FLAG char

int this_line;				/* current line on this page */
int this_col;				/* current column */
int this_page;				/* current page */
int pagelen;				/* page length */
int pagewid;				/* paper width */
int lmargin,rmargin;			/* left and right margins */
int tmargin,bmargin;			/* top and bottom margins */
int cfuncmin;				/* C function minimum length */

int num_pages;				/* number of pages to print */
int first_page;				/* first page to print */

FLAG csource;				/* true if paging C sources, */
FLAG wordstar;				/* recognize WordStar commands */
FLAG lineno;				/* true if doing line numbers */
FLAG title;				/* true if top of page titles */
FLAG ffs;				/* use formfeeds */

int afile;				/* main text file */
int bfile;				/* include file */
int ofile;
struct _xfbuf xfbuf;			/* main file info (afile) */

char bfname[80];
char afname[80];			/* where we save ASCII filenames */
char ofname[80];
char header[134];			/* top of page header */
char subheader[134];			/* sub title, */

/* Control characters are used to specify a character string; these
perform various definable functions. The WS functions ^B, ^D, ^S, etc
are done explicitly if not defined here. */

#define CTLMAX 16			/* max size of string */

char ctlstr[32][CTLMAX + 1] = {		/* control character macros */
	"",		/* NUL, not accessible */
	"",		/* ^A */
	"",		/* ^B WS bold */
	"",		/* ^C */
	"",		/* ^D WS double */
	"",		/* ^E */
	"",		/* ^F */
	"",		/* ^G */

	"\010",		/* ^H BS */
	"\011",		/* ^I HT */
	"\012",		/* ^J LF */
	"",		/* ^K used by Index */
	"\014",		/* ^L FF */
	"\015",		/* ^M CR */
	"",		/* ^N */
	"",		/* ^O */

	"",		/* ^P used by Index */
	"",		/* ^Q */
	"",		/* ^R */
	"",		/* ^S */
	"",		/* ^T */
	"",		/* ^U WS underline */
	"",		/* ^V */
	"",		/* ^W */

	"",		/* ^X, WS strike out */
	"",		/* ^Y */
	"",		/* ^Z */
	"",
	"",
	"",
	"",
	""
};
static int _stack = 15000;

/* Process the input file. If no output is specified, write it to
the standard output. */

main(argc,argv)
int argc;
char *argv[];
{
char *p,arg[80],sw[80];
int i;
int arg_error;

	printf ("%s\r\n",SIGNON);

	allmem();
	if (! fastfile(3)) {			/* init the (buffered) file system */
		printf("Go buy more memory.\r\n");
		exit(1);
	}

	newdelim(" \t\r\n;,");			/* arg delimiter list, */
	pagelen= 66;				/* page length */
	lmargin= 8;				/* left margin */
	pagewid= 80;
	tmargin= 1;				/* top margin */
	bmargin= 8;				/* bottom margin */
	*header= '\0';
	*subheader= '\0';
	wordstar= 1;				/* recog dot commands */
	ffs= 1;					/* use formfeeds */
	arg_error= 0;				/* no error yet, */
	csource= 0;				/* not a C source, */
	cfuncmin= pagelen / 3;			/* C func minimum length */
	title= 0;				/* no title yet, */
	lineno= 0;				/* no line numbers yet */
	bfile= 0;
	first_page= 1;
	num_pages= 10000;

	strcpy(afname,"");			/* no file yet, */
	strcpy(ofname,"prn");			/* default to printer */

/* Process the command line. We must have a filename, and optionally one
of two options: list amount of memory, of add line numbers to the index. */

	while (--argc) {
		++argv;
		cpyarg(arg,*argv);		/* possible name, */
		strip_switch(sw,*argv);		/* get the switches, */
		p= sw;

		while (*p) switch (*p++) {

			case 'V': wordstar= 0; lmargin= 0; 
				printf(" - Vanilla text\r\n"); break;

			case 'M': lmargin= atoi(arg); *arg= '\0';
				printf(" - %d column left margin\r\n",lmargin); break;

			case 'F':
				ffs= 0;
				printf(" - LineFeeds instead of FormFeeds\r\n"); break;

			case 'E': lineno= 1; 
				printf(" - Line numbers\r\n"); break;

			case 'W': if (atoi(arg) > 0) pagewid= atoi(arg); *arg= '\0';
					printf(" - Paper width %d columns\r\n",pagewid); break;

			case 'L': if (atoi(arg) > 0) pagelen= atoi(arg); *arg= '\0';
					printf(" - Paper length %d lines\r\n",pagelen); break;

			case 'C': csource= 1; lmargin= 0; 
				if (isdigit(*arg)) cfuncmin= atoi(arg); 
				*arg= '\0';
				printf(" - C source file, minimum function length %d lines\r\n",cfuncmin); break;

			case 'T': title= 1; 
				printf(" - Top of page titles\r\n"); break;

			case 'O': strcpy(ofname,arg); *arg= '\0'; 
				printf(" - Output to %s\r\n",ofname); break;

			case 'S': first_page= atoi(arg); *arg= '\0';
				if (first_page < 1) first_page= 1;
				printf("- Start printing at page #%u\r\n",first_page); break;

			case 'N': num_pages= atoi(arg); *arg= '\0';
				if (num_pages < 1) num_pages= 1;
				printf("- Print only %u pages\r\n",num_pages); break;

			default: printf(" - what??\r\n");++arg_error; break;
		}
		if (*arg) strcpy(afname,arg);	/* set filename if not blank */
	}

	if (! *afname) {
		printf("* Which file to Print?!\r\n");
		++arg_error;

	} else {
		afile= open(afname,0);	/* open file, */
		if (afile == -1) {
			printf("\r\n* Can't find %s\r\n",afname);
			++arg_error;

		} else {
			ofile= creat(ofname,2);
			if (ofile == -1) {
				printf("\r\n* Can't write to %s\r\n",ofname);
				++arg_error;
			}
		}
	}
	if (arg_error) {
		printf("\r\nTF {options} filename\r\n");
		printf("filename is the file to print\r\n");
		printf("                       f/O  Output filename \"f\" (default \"PRN\")\r\n");
		printf("  /T  Page titles      n/M  Left margin n columns (default 8)\r\n");
		printf("  /E  Line numbers     n/N  Print only n pages (default all)\r\n");
		printf("  /V  Vanilla text     n/S  Start at Page n (default 1)\r\n");
		printf("  /F  Use LFs not FFs  n/C  C source, n lines/function (/C = 1/3 Page)\r\n");
		printf("                       n/W  Page Width, (default 80)\r\n");
		printf("                       n/L  Page Length, (default 66)\r\n");
		exit(1);
	}
	xfbuf.f_attrib= 0;
	_find(afname,0,&xfbuf);			/* get basic file info */

	print();
	if (this_line > 0) tof();		/* top of form, */
	close(afile); 
	close(ofile);
	exit(0);
}
/* Print each line to the printer, processing dot commands as we go. */

print() {

char *cp,ln[132];
int i;
int last_page;				/* previous page number */
int line_count;				/* total lines, */
int brackets;				/* bracket nesting count */
int tof_now;

	line_count= 0;
	this_page= 1;
	this_line= 0;
	this_col= 0;
	last_page= -1;
	tof_now= 0;
	brackets= 0;

	while (1) {
		if (bfile) {
			if (! rline(bfile,ln,sizeof(ln))) {
				bfile= 0;
				continue;
			}
		} else if (! rline(afile,ln,sizeof(ln))) break;

		if (last_page != this_page) {	/* local display */
			printf("At Page #%-4u\r",this_page);
			last_page= this_page;
		}

		if (wordstar) {
			if (scan_line(ln))	/* scan for dot commands, */
				continue; 	/* ignore if present, */
		}

		if (this_page >= first_page + num_pages)
			return;			/* quit if last page */

		++line_count;			/* count total lines,*/
		if (this_line == 0) {
			do_header();		/* do headers, */
			for (i= tmargin; i--;) lputc(LF); /* top margin */
		}

/* If a C source, scan for brackets; when we reach the 0th bracket
level again, go to top of form. */

		if (csource) {
			for (cp= ln; *cp; ++cp) switch (*cp) {
				case '{': ++brackets; break;
				case '}': if (--brackets == 0) tof_now= 1; break;
			}
		}


/* If a non blank line, do the left margin, line numbers, etc and print
the text. If a blank line, just do a line feed, for speed. */

		if (*ln) {
			for (i= lmargin; i--;) lputc(' ');
			if (lineno) lprintf("%4u: ",line_count + 1);
			lputs(ln);
		}
		lputs("\r\n");

/* If at the bottom of the page, go to the next page. This is either
beyond the edge of the paper, or a C source and the bracket count went
to zero. */
		if (this_line > pagelen - bmargin) tof();

/* If a C source, and the bracket count went to zero, change pages if the
function is the minimum number of lines long. */

		if (tof_now) {
			if (this_line >= cfuncmin) tof(); 
			tof_now= 0;
		}
	}
}
/* Go to top of form. Use the right method, repeated linefeeds or a formfeed. */

tof() {

	if (ffs) lputc(FF);
	else {
		lputc(CR);
		while (this_line < pagelen) lputc(LF);
	}
	++this_page;
	this_line= 0;
}
/* Compare two strings, of a given length. Check only for equality. Return
0 if equal, else 1. */

compl(length,first,last)
int length;
char *first,*last;
{

	for (; length > 0; length--)
	{	if (  (toupper(*first)) != (toupper(*last))  )
			return(1);
		++first; ++last;
	}
	return(0);
}

/* Scan for dot commands. When and if we find a dot command, fool the 
caller into thinking that the line we were passed is now empty. (Return true)
If we find a formfeed, treat it the same as a .pa. */

scan_line(s)
char *s;
{
int n,i;
char arg[80],c;

	if (*s =='.') {				/* must be first atom */
		cpyatm(arg,s);			/* a clean copy */
		s= next_arg(s);			/* for numbers, etc */
		n= atoi(s);			/* if it is a number */

		if (compl (3,arg,".pa") == 0) {
			tof();			/* top of form */

		} else if (compl(3,arg,".def") == 0) {		/* define control char */
			if ((n > 0) && (n < 32)) {		/* the char to define */
				for (i= 0; i < CTLMAX; i++) {
					s= next_arg(s);		/* insert chars until */
					if (! isdigit(*s)) break;/* end of string */
					c= atoi(s);		/* convert to a number, */
					if (! c) c= '\377';	/* special case nulls */
					ctlstr[n][i]= c;	/* store it */
				}
				ctlstr[n][i]= '\0';		/* terminate */
			}

		} else if (compl (3,arg,".im") == 0) {		/* binary image file */
			rawfile(s);

		} else if (compl (3,arg,".in") == 0) {
			if (bfile == 0) {			/* if one not open */
				cpyarg(bfname,s);		/* get clean filename */
				bfile= open(bfname,0);		/* open it, */
				if (bfile == -1) {
					bfile= 0;		/* cant open */
					printf("\07\r\n* Cant find .in file '%s'\r\n",bfname);
				}

			} else printf("\07\r\n* Cant nest .in files: %s from within %s\r\n",
				s,bfname);


		} else if (compl (3,arg,".pn") == 0) {
			if (n > 0) this_page= n;	/* set if reasonable */

		} else if (compl (3,arg,".pl") == 0) {
			if (n > 10) pagelen= n;
			if (bmargin > pagelen) bmargin= 1;

		} else if (compl (3,arg,".pw") == 0) {
			if (n > 10) pagewid= n;

		} else if (compl (3,arg,".mt") == 0) {
			if ((n > 0) && (n < 40))	/* get top margin, */
				tmargin= n;		/* set if reasonable */

		} else if (compl (3,arg,".mb") == 0) {
			if (n < pagelen)		/* set bottom margin */
				bmargin= n;

		} else if (compl (3,arg,".ti") == 0) {
			title= 1;

		} else if (compl (3,arg,".he") == 0) {
			strcpy(header,s);

		} else if (compl (3,arg,".sh") == 0) {
			strcpy(subheader,s);

		} else if (compl (3,arg,".lm") == 0) {
				if (n <80) lmargin= n;
		}
		return(1);
	}
	return(0);
}
/* Send a raw file to the printer. The first line should contain a number
specifying the number of lines it occupies. */

rawfile(t)
char *t;
{
int i,f;
char ln[80];

	cpyarg(ln,t);				/* clean filename */
	f= open(ln,0);

	if (f != -1) {
		*ln= '\0';			/* in case its empty */
		rline(f,ln,sizeof(ln));		/* read a line, */
		this_line += atoi(ln);		/* just assume its a number */

		while (i= read(f,ln,sizeof(ln))) {
			write(ofile,ln,i);
		}
		close(f);
	}
}
/* Print the top of page header. If we find a #, replace it with 
the current page number. */

do_header() {

char buff[80];

	if (title) {
		lputs(afname);			/* write filename, */

		date(buff,xfbuf.date);		/* build date & time string, */
		time(buff[strlen(buff) - 1],xfbuf.time);
		while (this_col < (pagewid / 2) - (strlen(buff) / 2)) 
			lputc(' ');		/* center it on the page */
		lputs(buff);			/* write date & time */

		sprintf(buff,"Page %d",this_page);
		while (this_col < pagewid - strlen(buff) - 2) lputc(' ');
		lputs(buff);
		lputs("\r\n");
	}
	if (*header) hdrtxt(header);
	if (*subheader) hdrtxt(subheader);

}
/* Print the header text, expanding line number (#) with left margins. */

hdrtxt(p)
char *p;
{
int i;

	for (i= lmargin; i--;) lputc(' ');

	while (*p) {
		if (*p == '#') lprintf("%d",this_page);
		else lputc(*p);
		++p;
	}
	lputs("\r\n");
}
/* Display the date from the packed word.

 15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0
 ------- year ------- --- mon --- ---- day -----
 */

char _mon[12][4] = {
	"Jan",
	"Feb",
	"Mar",
	"Apr",
	"May",
	"Jun",
	"Jul",
	"Aug",
	"Sep",
	"Oct",
	"Nov",
	"Dec" };

date(sp,v)
char *sp;
unsigned v;
{
int day,mon,yr;

	day= v & 0x1f;
	mon= (v >> 5) & 0x0f;
	yr= (v >> 9) & 0x7f;
	sprintf(sp,"%02u %s %2u ",day,_mon[mon - 1],yr+ 80);
}
/* Display MSDOS packed time
15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0
---- hour --- ----- mins ------- ----- secs ----
*/
time(sp,v)
char *sp;
unsigned v;
{
int hr,mins,sec;

	sec= v & 0x1f;
	mins= (v >> 5) & 0x3f;
	hr= (v >> 11) & 0x1f;
	sprintf(sp,"%02u:%02u ",hr,mins);
}
/* Print to the output file. */

lprintf(f)
char *f;
{
char buf[500];

	_spr(buf,&f);
	lputs(buf);
}

/* Output a string to the printer. */

lputs(p)
char *p;
{
	while (*p) lputc(*p++);
}
/* Write a character to the output file. This is the highest level of
character output; it does bold, underline, etc, as well as counting lines
and columns.

	Linefeeds count as lines, formfeeds as top of pages, but what is
actually output to the printer is gotten from the table.

	Bold, double strike and underline are done the WS way if there
is nothing defined for them in the table, else the table is output
explicitly.
*/

lputc(c)
char c;
{
char b;
int i;

	if (c == CR + 128) c= '\0';		/* delete WordStar soft CRs */

	if (c < ' ') { 
		switch(c) {
			case TAB: do lputc(' '); while (this_col % 8); c= '\0'; break;
			case LF: ++this_line; this_col= 0; break;
			case FF: 
			case CR: this_col= 0; break;
			case BS: if (this_col) --this_col; break;

		}
		for (i= 0; ctlstr[c][i]; i++) {	/* control char macros */
			b= ctlstr[c][i];	/* convert FFh to NUL */
			if (b == '\377') b= '\0';
			lputc3(b);
		}

	} else {
		if (this_col == pagewid - 1) c= '!';
		if (this_col >= pagewid) return;
		lputc3(c);
		++this_col;
	}
}

/* Lowest level of character IO. */

lputc3(c)
char c;
{
	if (this_page < first_page) return;

	if (! write(ofile,&c,1)) {
		printf("\r\n * Disk full!\r\n\07");
		return;
	}
}

