/* OUTPUT.C -- Line, Page, and Output Abstractions for pf *********************

	(C) 1982 Perfect Software, Inc.

	01/26/82	Version 1.00 by Barry A. Dobyns

******************************************************************************/

#include "pf.gbl"

LStartHeader()				/* begin header mode */
{
	headermode=TRUE;
	heacumnum= -1;
	heacumlen=0;

	NormalEnv("header",TRUE,FALSE,'l');
	env.preveleft=env.eleft=env.cureleft=pag.pleft;
	env.eright=pag.pright;
	env.iswrap=FALSE;
	LStartLine();
	PutOneCh(EXITON);
	}

LHdrCenter()				/* begin the centering section */
{
	if (!headermode) return;
	BreakTok();
	LPutWhite(0);
	heacumlen=lin.column;
	heacumnum=lin.numtoks-1;
	}

LHdrRight()				/* begin right justifying */
{
	MICA len, trialcen, cen, LGetCol();

	if (!headermode) return;
	BreakTok();
						/* (l + (l+c))/2  want  l + c/2 */
	trialcen=(heacumlen+LGetCol())/2;
	cen=(pag.pright+pag.pleft)/2;
	if (trialcen<cen) {
		len=cen-trialcen+CHARRES/2;
		len-=len%CHARRES;
		lin.toks[heacumnum].length=len;
		lin.column+=len;
		}
	LPutWhite(0);
	heacumlen=lin.column;
	heacumnum=lin.numtoks-1;
	}

LHdrLine()				/* exit header mode */
{	
	MICA LGetCol();

	if (!headermode) return;
						/* right justify the rest */
	BreakTok();
	if (LGetCol()<pag.pright) {
		lin.toks[heacumnum].length=pag.pright-LGetCol();
		lin.column+=pag.pright-LGetCol();
		}
	BreakLine();
	}

LEndHeader()				/* exit header mode */
{
	PutOneCh(EXITOFF);
	BreakLine();
	EPop();
	headermode=FALSE;
	}

LFootnote()				/* set up for doing a footnote */
{
	movmem(&lin,&linsave,sizeof(lin));
	lin.numtoks=0;
	}

LNoFootnote()				/* finish doing a footnote */
{
	movmem(&linsave,&lin,sizeof(lin));
	}

LBreakLine()				/* make the current line a blank one */
{
	if (lin.numtoks>0) LNewLine();
	}

LFree(a)					/* free up a line's memory */
	LINEDATA *a;
{
	struct buftok *tptr;

	for (tptr= &a->toks[a->numtoks-1]; tptr>=a->toks; tptr--)
		if (tptr->line) SFree(tptr->line);
	MemFree(a);
	}

MICA
LGetCol()					/* return the current column */
{
	if (lin.numtoks>0 && lin.toks[lin.numtoks-1].line)
		return(lin.column-Width(SP));
	else return(lin.column);
	}

MICA
LGetLength(token,col)		/* return the length of TOKEN which
						   starts in COL */
	char *token;
	MICA col;
{
	MICA cur;

	if (!token) return(0);
	--token;
	cur=col;
	while (*++token) cur+= *token!=TAB ? Width(*token) : TabWidth(cur);
	return(cur-col);
	}

LNewLine()				/* flush the current line and start a new one */
{
	if (lin.column>MAXMICA) LStartLine();
	if (lin.howjustify=='b') lin.howjustify='l';
	PPutLine();
	}

LStartLine()				/* initialize a line */
{
	lin.numtoks=0;
	lin.column=env.eleft;
	lin.lleft=env.eleft;
	lin.lright=env.eright;
	lin.howjustify=env.justifytype;
	lin.lspace=env.linespacing;
	issuper= curscript=='p';
	issub= curscript=='b';
	}

LINEDATA *
LGetLine()				/* put the current line someplace */
{
	LINEDATA *ptr;
	int len;

	if (lin.numtoks==0) return(NULL);
	len=sizeof(*ptr) - (MAXTOKENS - lin.numtoks) * sizeof(lin.toks[0]);
	ptr=MemGet(len);
	movmem(&lin,ptr,len);
	ptr->column=LGetCol();

	env.eleft=env.cureleft;
	LStartLine();
	return(ptr);
	}

LPutToken(token)			/* put a token on the line */
	STRING *token;
{
	MICA len;
	char *cptr;
	struct buftok *tptr;

	if (lin.numtoks==0) LStartLine();
	len=LGetLength(SSToC(token),lin.column);
	if (env.iswrap && lin.column+len > lin.lright+CHARRES/2) {
		PPutLine();
		LStartLine();
		}
	if (lin.numtoks>=MAXTOKENS-1) {
		TIntError("Too many tokens for a single line");
		lin.numtoks=MAXTOKENS-20;
		}
	tptr= &lin.toks[lin.numtoks];
	if (lin.numtoks>=2 && !(tptr-1)->line && (tptr-2)->line &&
		 (tptr-1)->length==Width(SP)) {
		--lin.numtoks;
		--tptr;
		}
	tptr->length=len;
	tptr->line=token;
	lin.numtoks++;
	lin.column+=len+Width(SP);

	cptr=SSToC(token);
	while (*cptr)
		switch (*cptr++) {

		case SUPERON:
			issuper=TRUE;
			curscript='p';
			break;
		case SUBON:
			issub=TRUE;
			curscript='b';
			break;
		case SUPEROFF:
		case SUBOFF:
			curscript=NUL;
			break;
			}
	}

LPutWhite(amt)				/* put whitespace on the line */
	MICA amt;
{
	if (lin.numtoks==0) LStartLine();
	lin.column=LGetCol();
	if (lin.numtoks>=MAXTOKENS-1) {
		TIntError("Too many tokens for a single line");
		lin.numtoks=MAXTOKENS-20;
		}
	lin.toks[lin.numtoks].length=amt;
	lin.toks[lin.numtoks++].line=NULL;
	lin.column+=amt;
	}

/* -------------------------------------------------- */

PThisClose(amt)		/* return true if less than amt left on page */
int amt;
{
	return (pag.footerplace - pag.cumvert < amt);
	}

PFootnote()				/* put a footnote at the bottom of the page */
{
	pag.isfootnote=TRUE;
	LFootnote();
	pag.lastline=NULL;
	}

PFree()					/* free up a page's memory */
{
	LINEDATA **lptr;

	for (lptr= &pag.page[pag.numlines-1]; lptr>=pag.page; --lptr)
		if (*lptr) LFree(*lptr);
	for (lptr= &pag.page[pag.footerline]; lptr< &pag.page[MAXLINES]; ++lptr)
		if (*lptr) LFree(*lptr);
	}

PNewPage()				/*generate a new page */
{
	STRING *hptr;
	FLAG wasfoot;

	wasfoot=pag.isfootnote;
	pag.isfootnote=FALSE;
	minpage=min(minpage,ASpace(PageSpace));

	if (pag.numlines>0) {
		OPutPage();
		PFree();
		}

	pagenum= ++(pagevar->value);
	TPut(SP);	
	TPutn(pagenum);

	pag.cumvert=pag.numlines=0;
	pag.endline=pag.footerline=MAXLINES;
	pag.footerplace=pag.pbottom;

	pag.lastline=NULL;
	PPutVert(pag.ptop);
	hptr= pagenum&1 ? headodd : headeven;
	if (SLength(hptr)>3) {
		LStartHeader();
		IUse(SSToC(hptr),"header");
		pf();
		LEndHeader();
		}
	pag.lastline=NULL;
	PPutVert(sty.headerspace);

	pag.isfootnote=TRUE;
	pag.lastline=NULL;
	PPutVert(sty.footerspace);
	hptr= pagenum&1 ? footodd : footeven;
	if (SLength(hptr)>3) {
		LStartHeader();
		IUse(SSToC(hptr),"footer");
		pf();
		LEndHeader();
		}
	pag.isfootnote=FALSE;
	pag.endline=pag.footerline;

	nofootnotes=TRUE;
	pag.lastline=NULL;

	if (blankpages-- > 0) PNewPage();
	blankpages=0;
	pag.isfootnote=wasfoot;
	}

PNoFootnote()				/* exit footnote mode */
{
	LNoFootnote();
	pag.isfootnote=FALSE;
	pag.lastline=NULL;
	}

PPutIt(ptr,vert)			/* put something onto the current page */
	LINEDATA *ptr;
	MICA vert;
{
	int i;
	int j;

	vert+=dev.micavert/2;
	vert-=vert%dev.micavert;
	if (pag.footerline<=pag.numlines) {
		TIntError("Too many lines");
		PNewPage();
		}
	if (pag.cumvert+vert>=pag.footerplace) {
		if (pag.isfootnote)
			TError("Warning: Footnote page wrap not handled properly",NULL,
				NULL);
		PNewPage();
		}
	if (pag.isfootnote) {
		for (i = --pag.footerline; i<pag.endline-1; ++i) {
			pag.page[i]=pag.page[i+1];
			pag.vertical[i]=pag.vertical[i+1];
			}
		pag.lastline= &pag.page[pag.lastindex=pag.endline-1];
		*pag.lastline=ptr;
		pag.vertical[pag.endline-1]=vert;
		pag.footerplace-=vert;
		}
	else {
		pag.lastline= &pag.page[pag.lastindex=pag.numlines];
		*pag.lastline=ptr;
		pag.vertical[pag.numlines++]=vert;
		pag.cumvert+=vert;
		}
	}

PPutLine()				/* accept a line onto the current page */
{
	MICA minheight;
	FLAG sub;

	minheight=LINEHEIGHT/2;
	if (dev.micavert>minheight) minheight=dev.micavert;
	if (issuper && sty.scriptpush) PPutVert(minheight);
	sub=issub;			/* cache it before it is bashed */
	PPutIt(LGetLine(),lin.lspace);
	if (sub && sty.scriptpush) PPutVert(minheight);
	}

PPutVert(len)				/* leave LEN micas of whitespace vertically */
	MICA len;
{
	MICA olddist;

	if (pag.lastline && !*pag.lastline) {
		olddist=pag.vertical[pag.lastindex];
		if (olddist>len) return;
		if (pag.cumvert+len-olddist<pag.footerplace) {
			pag.vertical[pag.lastindex] = len;
			pag.cumvert += len-olddist;
			return;
			}
		}
	PPutIt(NULL,len);
	}

/* -------------------------------------------------- */

OPutPage()				/* write a page out */
{
	int i;

	if (outnotinitied && whereto=='f' && !dev.isfile) {
		OPuts("Perfect Format output for device: ");
		OPuts(dev.dname);
		OPut(CR);
		OPut(LF);
		OPut(EOF);
		outnotinited=FALSE;
		}
	if (pausehack && whereto != 'f'){
#ifdef LATTICE
		if ( whereto == 'p' ) fflush(prn);
#endif
		puts ("\nInsert fresh page; ");
		TypeACR();
		}
	for (i=0; i<pag.numlines; ++i) OPutLine(i);
	OPutVert(pag.footerplace-pag.cumvert);
	for (i=pag.footerline; i<MAXLINES; ++i) OPutLine(i);
/*	if (whereto=='f' && !dev.isfile) OPut(FF);	*/
	/* Thus, when used with -p or -c FF charaters appear in
	   output also if appropriate. */
	if ((dev.formfeed || whereto=='f') && !dev.isfile) OPut(FF);
	else OPutVert(dev.dheight-pag.pbottom);
	}

OPutLine(indx)				/* write out a single line */
	int indx;
{
	MICA tmica;
	int i, j, evenspace, oddstart, oddstop;
	LINEDATA *curline;
	STRING *curtok;
	char justflag;
	FLAG outside;

	curline=pag.page[indx];
	if (!curline) {
		OPutVert(pag.vertical[indx]);
		return;
		}
	OPutVert(pag.vertical[indx]-LINEHEIGHT);
	OPutHoriz(curline->lleft);
	justflag=SP;
	switch (curline->howjustify) {

	case 'c':
		OPutHoriz((curline->lright - curline->column)/2);
		break;
	case 'r':
		OPutHoriz(curline->lright - curline->column);
		break;
	case 'b':
		if (whereto=='f' && !dev.isfile) {
			if (curline->column <= curline->lright) {
				OPut(JUSTIFY);
				tmica=curline->lright - curline->column;
				OPut(tmica>>8);
				OPut(tmica&255);
				justflag=TOKBREAK;
				}
			}
		else {
			justflag=NUL;
			if (curline->lright > curline->column)
				tmica=curline->lright - curline->column;
			else tmica=0;
			evenspace=(tmica+Width(SP)/2)/Width(SP);
			i=OCntBrk(curline);
			oddstart=curline;			/* get random number */
			oddstart=((oddstart*17)&0x7FFF) % i;
			oddstop=oddstart+evenspace%i;
			outside= oddstop>=i;
			oddstop %= i;
			evenspace=evenspace/i;
			}
		break;
		}
	j=0;
	for (i=0; i<curline->numtoks-1; i++) {
		curtok=curline->toks[i].line;
		if (curtok) {
			OPuts(SSToC(curtok));
			if (justflag && curline->toks[i+1].line) OPut(justflag);
			if (!justflag) {
				if (curline->toks[i+1].line)
					OPutHoriz((evenspace+1)*Width(SP));
				else OPutHoriz(evenspace*Width(SP));
				if ((!outside && (oddstart<j && j<=oddstop)) ||
					(outside && (j<=oddstop || oddstart<j))) OPut(SP);
				++j;
				}
			}
		else OPutHoriz(curline->toks[i].length);
		}
	if (curtok=curline->toks[curline->numtoks-1].line) OPuts(SSToC(curtok));
	if (whereto=='f' || dev.isfile) {
		OPut(CR);
		OPut(LF);
		}
	else OPutCom(&dev.donewline);
	}

int
OCntBrk(curline)			/* count number of spots to justify in */
	LINEDATA *curline;
{
	int i, j;

	j=curline->numtoks-1;
	for (i=j; i>=0; --i) if (!curline->toks[i].line) --j;
	return(j);
	}

OPutVert(amt)				/* write out vertical whitespace */
	MICA amt;
{
	int i;

	if (amt>MAXMICA || amt==0) return;
	if (whereto=='f' && !dev.isfile) {
		OPut(VERTSPACE);
		OPut(amt >> 8);
		OPut(amt & 255);
		}
	else {
		for (i=(amt+LINEHEIGHT/2)/LINEHEIGHT; i; --i) {
			OPut(CR);
			OPut(LF);
			}
		}
	}

OPutHoriz(amt)				/* put some blanks in the buffer */
	MICA amt;
{
	MICA i, j;

	if (amt>MAXMICA || amt==0) return;
	if (whereto=='f' && !dev.isfile) {
		OPut(HORIZSPACE);
		OPut(amt >> 8);
		OPut(amt & 255);
		}
	else {
		j=Width(SP);
		for (i=(amt+j/2)/j; i; --i) OPut(SP);
		}
	}

OPut(chr)					/* put a char in the buffer */
	char chr;
{
	if (chr==SENTENCEBREAK && (dev.isfile || whereto!='f')) chr=SP;
#ifdef CPM
	if (whereto=='c' && chr<=DEL) putc(chr,1);
	else if (whereto=='p' && chr<=DEL) putc(chr,2);
	else if (whereto=='f' && (!dev.isfile || chr<=DEL)) WPut(chr);
#else
#ifdef LATTICE
	if (whereto=='c' && chr<=DEL) putchar(chr);
	else if (whereto=='p' && chr<=DEL) putc(chr, prn);
	else if (whereto=='f' && (!dev.isfile || chr<=DEL)) WPut(chr);
#else
	if (whereto=='c' || whereto=='p') { if(chr>0) putchar(chr); }
	else if (!dev.isfile || chr>0) WPut(chr);
#endif
#endif
	}

OPuts(a)					/* put a string in the buffer */
	char *a;
{
	while (*a) OPut(*a++);
	}

OPutCom(cmd)				/* send a command string */
	struct comstr *cmd;
{
	char *chr, *endchr;

	chr= &dev.strspc[cmd->idx];
	endchr= &dev.strspc[cmd->idx + cmd->len];
	while (chr<endchr) OPut(*chr++);
	}

WPut(chr)					/* buffer a character */
	char chr;
{
	*outchar++ =chr;
	if (outchar> &outputbuffer[BUFFSIZE-1]) WFlush();
	}

WFlush()					/* write a record out */
{
	int nrecs;

	if (outchar==outputbuffer) return;
	nrecs=(outchar - outputbuffer + (RECSIZE-1))/RECSIZE;
	if (write(outfd,&outputbuffer,nrecs) < nrecs)
		TError("Output file write error.",NULL,NULL);
	outchar=outputbuffer;
	}

/* END OF OUTPUT.C */
