/*
	trf-flush.c - state and content text flushing

	Eventually, all output will end up being flushed through one
	or two low-level routines, which will allow better trapping
	on conditions such as initial state needing to be dumped,
	diversions needing to be started, trap positions needing to
	be flushed, etc.  Maybe in 1.06a2.
*/

# include	<stdio.h>
# include	<sys/types.h>
# ifdef	VARARGS
# include	<varargs.h>
# endif	/* VARARGS */
# include	"rtf.h"
# include	"rtf2troff.h"


static int	initialStateFlushed = 0;

static int	useInLine = 0;	/* non-zero for inline char state changes */
static char	inLineChgs[rtfBufSiz] = "";

/*
	Whether any content text chars have been written to current
	paragraph.
*/

static int	inPara = 0;
static int	oLen = 0;
static int	breakOK = 0;


static void	FlushDocState ();
static void	FlushParState ();
static void	FlushCharState ();
static void	FlushSACharState ();
static void	Continuation ();
static void	CalcInLineChanges ();
static void	_PutS ();
static char	*ApplyIndirection ();

static void	DrawLine ();
static char	*TabTypeStr ();
static char	*JustTypeStr ();
static void	CheckVMargins ();
static double	LineLen ();


/*
	Flush any discrepancies between state as written and current
	internal state to bring the former in sync with the latter.

	Virtually all formatting text is written by this operation.

	It's assumed here, perhaps unfortunately, that things needing
	a Flush() first won't occur in the middle of output line
	collection.
*/

void FlushState ()
{
	/* flush */
	FlushInitialState ();
	if (docStateChanged)
		FlushDocState ();
	/* header/footer depend on some doc properties */
	if (docStateChanged || sectStateChanged)
		FlushSectState ();
	/* para line length depends on some doc properties; ditto tabs */
	if (docStateChanged || parStateChanged)
		FlushParState ();
	if (charStateChanged)
		FlushCharState ();

	/* sync */
	if (docStateChanged)
		bcopy ((char *) ids, (char *) wds, (int) sizeof (DocState));
	if (sectStateChanged)
		bcopy ((char *) iss, (char *) wss, (int) sizeof (SectState));
	if (parStateChanged)
		bcopy ((char *) ips, (char *) wps, (int) sizeof (ParState));
	if (charStateChanged)
		bcopy ((char *) ics, (char *) wcs, (int) sizeof (CharState));

	docStateChanged = 0;
	sectStateChanged = 0;
	parStateChanged = 0;
	charStateChanged = 0;
}


/*
	This is called at the beginning of output to write out absolute
	initial values for some important state stuff.  The other
	state-writers usually write values relative to the last written
	values, so this is needed to write absolute values that the
	relative values can be relative *to*.

	Problem: it's important to avoid tripping the first pseudo-page
	transition, or the header for the first page will be lost.  This
	occurs when non-diverted text processing occurs or when a number
	of different requests (e.g., .in) occur.  Header/footer text
	processingn occurs in diversions, so that's not a problem.  To
	avoid tripping the trap with requests, use things like 'in instead
	of .in.  Losing a break isn't a problem since there's no content
	text to write yet.

	The page length is written early and header/footer traps are
	planted.  These traps stay intact.  At most, the footer trap
	position might be moved.

	Tp is non-zero if a section's title page is special.

	Macros Ha, Hf, Hl and Hr are defined if/when all-page, first-page,
	left-page and right-page headers are given, and the number registers
	of the same name, which initially have value zero, are set to 1.
	Similarly for footers.  The register Tm defines the top margins.
	The registers Hp and Fp define the header and footer positions.

	HE, FO need are written to exit if there are trap loops and
	to not space too much if vertical margins are weird.
*/

void FlushInitialState ()
{
	if (initialStateFlushed)
		return;

	Comment ("begin initial layout setup, change as desired");

	/* check whether it appears landscape *should have* been selected */
	if (ids->pageHeight < ids->pageWidth && !ids->landscape)
	{
		fprintf (stderr, "Turning landscape on\n");
		ids->landscape = 1;
	}

	if (ids->landscape)
	{
		if (tvers == XROFF)
			fprintf (f, ".dc landscape\n");
		/* reverse page height and width? */
	}
	fprintf (f, ".pl %gi\n", ids->pageHeight);
	if (tvers == XROFF)
	{
	double	pLen;

		/* have to tell printer the page length in 300dpi units */
		/* if not default 11in (this is orientation dependent) */
		if (ids->landscape)
			pLen = ids->pageWidth;
		else
			pLen = ids->pageHeight;
		if (pLen != 11.0)
			fprintf (f, ".dc length %d\n", (int) (pLen * 300));
	}

	/* abandon hope, all ye who who enter here to try to read this... */

	fprintf (f, ".ad %s\n", JustTypeStr (ips->justification));
	fprintf (f, ".po %gi\n", ids->leftMargin);
	fprintf (f, "'in %gi\n", ips->leftIndent);	/* ' to avoid break */
	fprintf (f, ".ll %gi\n", LineLen (ids, ips));
	fprintf (f, ".ps %d\n", ics->fontSize);
	Comment ("%gi = %gp", ips->spaceBetween, ips->spaceBetween * 72);
	fprintf (f, ".vs %gi\n", ips->spaceBetween);
	fprintf (f, ".ft R\n");

	/* plant traps */

	Comment ("plant header trap");
	fprintf (f, ".nr %s %d\n", rTitlePageSpecial, iss->titleSpecial);
	fprintf (f, ".nr %s %gi\n", rTopMargin, ids->topMargin);
	fprintf (f, ".nr %s %gi\n", rHeaderPos, iss->headerPos);
	fprintf (f, ".nr %s 0\n", rHeaderAll);
	fprintf (f, ".nr %s 0\n", rHeaderFirst);
	fprintf (f, ".nr %s 0\n", rHeaderLeft);
	fprintf (f, ".nr %s 0\n", rHeaderRight);
	fprintf (f, ".de %s\n", mHeader);
	fprintf (f, ".if \\\\n(%s>=\\\\n(Bm \\{\\\n",
					rTopMargin, rBottomMargin);
	fprintf (f, ".\ttm Trap Loop Death detected...\n");
	fprintf (f, ".\tex\n");
	fprintf (f, ".\\}\n");
	fprintf (f, ".rs\n");
	fprintf (f, ".if \\\\n(%s<\\\\n(%s 'sp |\\\\n(%su\n",
			rHeaderPos, rTopMargin, rHeaderPos);
	fprintf (f, ".ev 1\n");
	/*fprintf (f, ".nf\n");					/* correct? */
	/* ugly stuff to select correct header text macro */
	fprintf (f, ".ie (\\\\n%%=1&\\\\n(%s>0&\\\\n(%s>0) .%s\n",
			rHeaderFirst, rTitlePageSpecial, mHeaderFirst);
	fprintf (f, ".el \\{\\\n");
	fprintf (f, ".	ie \\\\n(%s>0 \\{\\\n", rHeaderLeft);
	fprintf (f, ".		ie o .%s\n", mHeaderRight);
	fprintf (f, ".		el .%s\n", mHeaderLeft);
	fprintf (f, ".	\\}\n");
	fprintf (f, ".	el .if \\\\n(%s>0 .%s\n", rHeaderAll, mHeaderAll);
	fprintf (f, ".\\}\n");
	/* end ugly stuff */
	fprintf (f, ".ev\n");
	fprintf (f, "'sp |\\\\n(%su\n", rTopMargin);
	fprintf (f, ".ns\n");
	fprintf (f, "..\n");
	fprintf (f, ".wh 0i %s\n", mHeader);

	Comment ("plant footer trap");
	fprintf (f, ".nr %s %gi\n",
			rBottomMargin, ids->pageHeight - ids->bottomMargin);
	fprintf (f, ".nr %s %gi\n",
			rFooterPos, ids->pageHeight - iss->footerPos);
	fprintf (f, ".nr %s 0\n", rFooterAll);
	fprintf (f, ".nr %s 0\n", rFooterFirst);
	fprintf (f, ".nr %s 0\n", rFooterLeft);
	fprintf (f, ".nr %s 0\n", rFooterRight);
	fprintf (f, ".de %s\n", mFooter);
	fprintf (f, ".if \\\\n(%s>\\\\n(%s 'sp |\\\\n(%su\n",
			rFooterPos, rBottomMargin, rFooterPos);
	fprintf (f, ".ev 1\n");
	/*fprintf (f, ".nf\n");					/* correct? */
	/* ugly stuff to select correct footer text macro */
	fprintf (f, ".ie (\\\\n%%=1&\\\\n(%s>0&\\\\n(%s>0) .%s\n",
			rFooterFirst, rTitlePageSpecial, mFooterFirst);
	fprintf (f, ".el \\{\\\n");
	fprintf (f, ".	ie \\\\n(%s>0 \\{\\\n", rFooterLeft);
	fprintf (f, ".		ie o .%s\n", mFooterRight);
	fprintf (f, ".		el .%s\n", mFooterLeft);
	fprintf (f, ".	\\}\n");
	fprintf (f, ".	el .if \\\\n(%s>0 .%s\n", rFooterAll, mFooterAll);
	fprintf (f, ".\\}\n");
	/* end ugly stuff */
	fprintf (f, ".ev\n");
	fprintf (f, "'bp\n");
	fprintf (f, "..\n");
	fprintf (f, ".wh %gi %s\n", -ids->bottomMargin, mFooter);

	Comment ("end initial layout setup");

	/* manually sync everything that was just flushed */
	wds->bottomMargin = ids->bottomMargin;
	wds->bottomMargin = ids->bottomMargin;
	wds->landscape = ids->landscape;
	wds->leftMargin = ids->leftMargin;
	wds->leftMargin = ids->leftMargin;
	wds->pageHeight = ids->pageHeight;
	wds->pageWidth = ids->pageWidth;
	wds->rightMargin = ids->rightMargin;
	wds->topMargin = ids->topMargin;
	wss->footerPos = iss->footerPos;
	wss->headerPos = iss->headerPos;
	wss->titleSpecial = iss->titleSpecial;
	wps->justification = ips->justification;
	wps->leftIndent = ips->leftIndent;
	wps->rightIndent = ips->rightIndent;
	wps->spaceBetween = ips->spaceBetween;
	wcs->fontSize = ics->fontSize;

	++initialStateFlushed;
}


/*
	Note that right margin is document property in RTF, but has the
	effect of changing line length, which is handled under paragraph
	property changes.  Ditto for change of default tab width.
*/

static void FlushDocState ()
{
	CheckVMargins ();
	if (ids->landscape != wds->landscape)
	{
		/* note: once on, can't turn off */
		if (ids->landscape)	/* it's now on */
		{
			Flush ();
			if (tvers == XROFF)
				fprintf (f, ".dc landscape\n");
		}
	}
	if (ids->pageHeight != wds->pageHeight)
	{
		Flush ();
		fprintf (f, ".pl %gi\n", ids->pageHeight);
		if (tvers == XROFF)
		{
		double	pLen;

		/* have to tell printer the page length in 300dpi units */
		/* if not default 11in (this is orientation dependent) */

			if (ids->landscape)
				pLen = ids->pageWidth;
			else
				pLen = ids->pageHeight;
			if (pLen != 11.0)
				fprintf (f, ".dc length %d\n",
							(int) (pLen * 300));
		}
	}
	if (ids->leftMargin != wds->leftMargin)
	{
		Flush ();
		fprintf (f, ".po %gi\n", ids->leftMargin);
	}
}


/*
	If the top margin or the header or footer positions have
	changed, redefine the registers giving their sizes.  If the
	bottom margin has changed, move the trap to the right spot.
	(Document and section state interact here.)

	This is also called when a macro is about to be diverted, so that
	the trap position isn't set within a different environment.
	(Is that necessary?)

	The really ugly thing here is to try and catch cases where the
	header position is set below the top margin, and especially where the
	footer position *above* the bottom margin.  The latter can result
	in loops where the footer trap is invoked in a loop.
*/

void FlushSectState ()
{
	if (iss->titleSpecial != wss->titleSpecial)
	{
		Flush ();
		fprintf (f, ".nr %s %d\n",
				rTitlePageSpecial, iss->titleSpecial);
	}
	if (ids->topMargin != wds->topMargin)
	{
		Flush ();
		fprintf (f, ".nr %s %gi\n", rTopMargin, ids->topMargin);
	}
	if (iss->headerPos != wss->headerPos)
	{
		Flush ();
		fprintf (f, ".nr %s %gi\n", rHeaderPos, iss->headerPos);
	}
	if (ids->bottomMargin != wds->bottomMargin)
	{
		Flush ();
		fprintf (f, ".ch %s %gi\n", mHeader, -ids->bottomMargin);
	}
	if (iss->footerPos != wss->footerPos)
	{
		Flush ();
		fprintf (f, ".nr %s %gi\n",
				rFooterPos, ids->pageHeight - iss->footerPos);
	}
}


static void FlushParState ()
{
int	tabdiff;
int	i;

	if (ips->justification != wps->justification)
	{
		Flush ();
		fprintf (f, ".ad %s\n", JustTypeStr (ips->justification));
	}
	if (ips->leftIndent != wps->leftIndent)
	{
		Flush ();
		fprintf (f, ".in %+gi\n", ips->leftIndent - wps->leftIndent);
	}
	/*
		troff doesn't set right indent, rather it sets
		line length (function of page width - po - rm - ri)
	*/
	if (ids->pageWidth != wds->pageWidth
		|| ids->leftMargin != wds->leftMargin
		|| ids->rightMargin != wds->rightMargin
		|| ips->rightIndent != wps->rightIndent)
	{
		Flush ();
		fprintf (f, ".ll %gi\n", LineLen (ids, ips));
	}
	if (ips->spaceBetween != wps->spaceBetween)
	{
		Flush ();
		fprintf (f, ".vs %gi\n", ips->spaceBetween);
	}

	/*
		Determine if tabs have changed, which they will if there
		are a different number of tab stops than previously, or any
		of the current ones are different than those last written
		out.  Change of default width is a change, too.
	*/

	tabdiff = 0;
	if (ids->tabWidth != wds->tabWidth)
		tabdiff = 1;
	else if (ips->nTabs != wps->nTabs)
		tabdiff = 1;
	else
	{
		for (i = 0; i < ips->nTabs; i++)
		{
			if (ips->tab[i] != wps->tab[i]
				|| ips->tabType[i] != wps->tabType[i])
			{
				tabdiff = 1;
				break;
			}
		}
	}
	if (tabdiff)
	{
		Flush ();
		if (ips->nTabs == 0)		/* use defaults */
		{
			fprintf (f, ".ta %gi", ids->tabWidth);
			for (i = 1; i < maxTab; i++)
				fprintf (f, " +%gi", ids->tabWidth);
		}
		else
		{
			fprintf (f, ".ta %gi%s", ips->tab[0],
						TabTypeStr (ips->tabType[0]));
			for (i = 1; i < ips->nTabs; i++)
			{
				fprintf (f, " +%gi%s",
						ips->tab[i] - ips->tab[i-1],
						TabTypeStr (ips->tabType[i]));
			}
		}
		fprintf (f, "\n");
	}
	if (ips->tabChar != wps->tabChar)
	{
		Flush ();
		switch (ips->tabChar)
		{
		case rtfLeaderMotion:
			fprintf (f, ".tc\n");
			break;
		case rtfLeaderDot:
			fprintf (f, ".tc .\n");
			break;
		case rtfLeaderHyphen:
			fprintf (f, ".tc -\n");
			break;
		case rtfLeaderUnder:
		case rtfLeaderThick:
			fprintf (f, ".tc _\n");
			break;
		}
	}
}


/*
	Flush character state.  Actually, if useInLine is true, this
	just calculates the string of inline commands that should be
	generated, and those are later flushed in PutString ().
*/

static void FlushCharState ()
{
	if (useInLine)
		CalcInLineChanges ();
	else
		FlushSACharState ();
}


/*
	Flush character state, using standalone requests.
	If in a paragraph, generates a \c to cause stuff on current line
	to be joined to next so extraneous space won't end up in the
	output.
*/
static void FlushSACharState ()
{
u_long	csFontBits, wsFontBits;
int	idiff;
double	ddiff;

	if (ics->fontSize != wcs->fontSize)	/* write font size */
	{
		Continuation ();
		idiff = ics->fontSize - wcs->fontSize;
		fprintf (f, ".ps %+d\n", idiff);
	}
	/*
		Note: super/subscripts don't always have intended effect
		in non-inline mode.  Output may need hand fixing.
	*/
	if (ics->superScript != wcs->superScript)
	{
		Continuation ();
		ddiff = wcs->superScript - ics->superScript;
		fprintf (f, "'sp %gp\n", ddiff);
	}
	if (ics->subScript != wcs->subScript)
	{
		Continuation ();
		ddiff = ics->subScript - wcs->subScript;
		fprintf (f, "'sp %gp\n", ddiff);
	}
	if (ics->charStyle != wcs->charStyle)	/* write R, I, B */
	{
		/*
			Since troff implements plain, bold and italic by
			changing fonts, figure out whether the font needs
			to be changed.  This doesn't understand simultaneous
			bold+italic (boo-hoo), and treats it as italic.
		*/
		csFontBits = StyleFontBits (ics->charStyle);
		wsFontBits = StyleFontBits (wcs->charStyle);
		if (csFontBits != wsFontBits)
		{
			Continuation ();
			if (csFontBits == 0)	/* neither bold or italic */
				fprintf (f, ".ft R\n");
			else if (csFontBits & styleItalic)
				fprintf (f, ".ft I\n");
			else if (csFontBits & styleBold)
				fprintf (f, ".ft B\n");
		}

		/* if smallcaps now on and wasn't before, turn on */
		if ((ics->charStyle & styleSmallCaps)
			&& !(wcs->charStyle & styleSmallCaps))
		{
			Continuation ();
			fprintf (f, ".ps -1\n");
		}
		/* if smallcaps now off and wasn't before, turn off */
		if (!(ics->charStyle & styleSmallCaps)
			&& (wcs->charStyle & styleSmallCaps))
		{
			Continuation ();
			fprintf (f, ".ps +1\n");
		}
	}
}


static void Continuation ()
{
	if (oLen > 0)
	{
		if (breakOK)
			fprintf (f, "\n");
		else
			fprintf (f, "\\c\n");	/* need ApplyIndirection() ? */
		ResetParLine ();
	}
}


/*
	Generate a string of inline-changes, which need to be flushed with
	indirection applied.
*/

static void CalcInLineChanges ()
{
char	*picp = inLineChgs;
int	csFontBits, wsFontBits;
int	idiff;
double	ddiff;
char	c;

	*picp = '\0';
	if (ics->fontSize != wcs->fontSize)	/* write font size */
	{
		idiff = ics->fontSize - wcs->fontSize;
		c = '+';
		if (idiff < 0)
		{
			c = '-';
			idiff *= -1;
		}
		while (idiff > 9)
		{
			sprintf (picp, "\\s%c9", c);
			picp += strlen (picp);
			idiff -= 9;
		}
		sprintf (picp, "\\s%c%d", c, idiff);
		picp += strlen (picp);
	}
	if (ics->superScript != wcs->superScript)
	{
		ddiff = wcs->superScript - ics->superScript;
		sprintf (picp, "\\v'%gp'", ddiff);
		picp += strlen (picp);
	}
	if (ics->subScript != wcs->subScript)
	{
		ddiff = ics->subScript - wcs->subScript;
		sprintf (picp, "\\v'%gp'", ddiff);
		picp += strlen (picp);
	}
	if (ics->charStyle != wcs->charStyle)	/* write R, I, B */
	{
		/*
			Since troff implements plain, bold and italic by
			changing fonts, figure out whether the font needs
			to be changed.  This doesn't understand simultaneous
			bold+italic (boo-hoo), and treats it as italic.
		*/
		csFontBits = ics->charStyle & (styleBold | styleItalic);
		wsFontBits = wcs->charStyle & (styleBold | styleItalic);
		if (csFontBits != wsFontBits)
		{
			if (csFontBits == 0)	/* neither bold or italic */
				sprintf (picp, "\\fR");
			else if (csFontBits & styleItalic)
				sprintf (picp, "\\fI");
			else if (csFontBits & styleBold)
				sprintf (picp, "\\fB");
			/* this is a NOP if no "if" was triggered above */
			picp += strlen (picp);
		}

		/* if smallcaps now on and wasn't before, turn on */
		if ((ics->charStyle & styleSmallCaps)
			&& !(wcs->charStyle & styleSmallCaps))
		{
			sprintf (picp, "\\s-1");
			picp += strlen (picp);
		}
		/* if smallcaps now off and wasn't before, turn off */
		if (!(ics->charStyle & styleSmallCaps)
			&& (wcs->charStyle & styleSmallCaps))
		{
				sprintf (picp, "\\s+1");
				picp += strlen (picp);
		}
	}
}


/*
	Save font, point size and vertical spacing.  Called at beginning
	of table to get an idea of the values for the parameters that tbl
	will use at the beginning of each cell.  FlushTblFPV() is called
	after each cell is begin, to undo this if the previous cell ends
	with some different values, so those values will carry through.
*/


static double	vs;
static int	ps;
static u_long	font;


void SaveTblFPV ()
{
	FlushState ();		/* make sure internal state same as written */
	vs = ips->spaceBetween;
	ps = ics->fontSize;
	font = StyleFontBits (ics->charStyle);
}


void FlushTblFPV ()
{
u_long	curFont;

	if (1 || ips->spaceBetween != vs)	/* tbl will have set it to vs, */
	{				/* so set it back */
		fprintf (f, ".vs %gi\n", ips->spaceBetween);
		wps->spaceBetween = ips->spaceBetween;
	}
	if (1 || ics->fontSize != ps)	/* tbl will have... */
	{
		fprintf (f, ".ps %d\n", ics->fontSize);
		wcs->fontSize = ics->fontSize;
	}
	curFont = StyleFontBits (ics->charStyle);
	if (1 || curFont != font)		/* tbl will have... */
	{
		if (curFont == 0)
			fprintf (f, ".ft R\n");
		else if (curFont & styleItalic)
			fprintf (f, ".ft I\n");
		else if (curFont & styleBold)
			fprintf (f, ".ft B\n");
		/* now the hard part */
		wcs->charStyle &= ~StyleFontBits (wcs->charStyle);
		wcs->charStyle |= curFont;
	}
}


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


void ResetPar ()
{
	inPara = 0;
	ResetParLine ();
}


void ResetParLine ()
{
	oLen = 0;
	breakOK = 0;
}


/*
	Unconditional flush -- force output line and prevent next line
	from being joined to it.  Also handle any bottom border and
	"extra space after paragraph" if any is needed.
*/

void Par ()
{
	FlushInitialState ();
	if (inPara)
		fprintf (f, "\n.br\n");
	else
		fprintf (f, ".sp\n");
	ResetPar ();
	if (ips->borderType != rtfNoBorderType
		&& (ips->borderFlags & borderBottom) != 0)
	{
		/* draw bottom border */
		DrawLine (ips->borderType);
	}

	if (ips->spaceAfter != 0.0)
		fprintf (f, ".sp %gi\n", ips->spaceAfter);

}


void Sect ()
{
char	*p = NULL;
char	buf[20];

	Par ();		/* finish current paragraph */
	switch (iss->breakType)
	{
	case rtfNoBreak:
		break;		/* nothing to do */
	case rtfColBreak:
		/* this is untested! */
		sprintf (buf, ".sp |\\n(%s\n", rBottomMargin);
		p = buf;
		break;
	case rtfPageBreak:
		p = ".bp";
		break;
	case rtfEvenBreak:
		p = ".if e .bp";
		break;
	case rtfOddBreak:
		p = ".if o .bp";
		break;
	}
	if (p != NULL)
	{
		FlushInitialState ();
		fprintf (f, "%s\n", p);
	}
}

/*
	Document content text writing routines.  These should not be
	used to write out formatting text.

	Flush()		force out any collected content text, if any
	PutString()	write out a string of characters
	PutFunnyChar()	map char > 127 onto troff equivalent
*/


void Flush ()
{
	if (inPara)
	{
		_PutS ("\n");
		ResetPar ();
	}
}


/*
	Dump out a piece of content text.  Argument should be a string just
	as you would write it normally, assuming no levels of indirection.

	Does state flushing, beginning-of-paragraph processing, flushes
	pending inline changes, and writes out the string (account for levels
	of indirection).

	Handles underlining if continuous underlining on, or word underlining
	is on and string isn't " " or "\ ".

	Does *not* do:
		special char mapping	(do before calling)
		to-caps mapping		(ditto)

*/

void PutString (s)
char	*s;
{
int	doUnderlining = 0;
int	doStrikeThru = 0;
char	*p;

	if (ics->charStyle & styleInvisible)
		return;

	if (stateChanged)
	{
		useInLine = 1;
		FlushState ();		/* clears stateChanged */
		useInLine = 0;
	}

	/*
		It's OK to hang onto inline changes until after this if-block
		since only paragraph properties are used here; inlines only
		affect character properties.
	*/

	if (inPara == 0)		/* just beginning a paragraph */
	{
		if (ips->spaceBefore != 0.0)
			fprintf (f, ".sp %gi\n", ips->spaceBefore);
		if (ips->borderType != rtfNoBorderType
			&& (ips->borderFlags & borderTop) != 0)
		{
			/* draw top border */
			DrawLine (ips->borderType);
		}
		if (ips->firstIndent != 0.0)
			fprintf (f, ".ti %gi\n", ips->firstIndent);
	}

	if (inLineChgs[0] != '\0')
	{
		_PutS (ApplyIndirection (inLineChgs));
		inLineChgs[0] = '\0';
	}

	/* Break up long output lines.  */
	if (oLen > lineBreakLen && breakOK && s[0] != ' ')
		_PutS ("\n");	/* (<-- turns breakOK off) */

	/*
		See if this is a natural breakpoint (single space not
		at beginning of line).  If so, remember it for following
		characters, so long lines can be broken.  If this is
		a breakpoint, but the previous character was too, then
		we're seeing multiple whitespace characters, and it's really
		not a breakpoint, since breaking the line would then result
		in loss of whitespace when troff joins lines back together
		(it tosses trailing whitespace; this is only safe when that
		consists of a single space).
	*/

	if (oLen > 0 && s[0] == ' ' && s[1] == '\0')
	{
		if (breakOK)
			breakOK = 0;	/* multiple whitespace; not OK */
		else
			breakOK = 1;
	}

	if (ics->charStyle & styleUnderline)
		++doUnderlining;
	if (ics->charStyle & styleStrikeThru)
		++doStrikeThru;
	else if (ics->charStyle & styleWUnderline)
	{
		if (strcmp (s, " ") != 0 && strcmp (s, "\\ ") != 0)
			++doUnderlining;
	}
	if (doUnderlining || doStrikeThru)
	{
		if (oLen > 0)	/* force onto own line if necessary */
		{
			p = ApplyIndirection ("\\c\n");
			_PutS (p);
		}
		/* mark horizontal position */
		p = ApplyIndirection ("\\kx");
		_PutS (p);
	}
	p = ApplyIndirection (s);
	_PutS (p);
	if (doUnderlining)
	{
		/* return to marked position, draw underline */
		p = ApplyIndirection ("\\l'|\\nxu\\(ul'");
		_PutS (p);
	}
	if (doStrikeThru)
	{
		/* return to marked position, draw strikethrough */
		p = ApplyIndirection ("\\v'-.2v'\\l'|\\nxu-'\\v'.2v'");
		_PutS (p);
	}

	inPara = 1;
}


/*
	Write something to current paragraph, keeping track of last char
	and number of characters written to current line.  Need oLen and
	breakOK to know when to break output line for readability.

	When a newline is written, oLen is reset.

	When a non-space is written, breakOK is turned off, which handles
	cases where PutString() saw a single space and thought a natural
	break was in order, but that space ends up coming out in the middle
	of control language, such as for underlining.
*/

static void _PutS (s)
char	*s;
{
char	c;

	while ((c = *s++) != '\0')
	{
		fputc (c, f);
		if (c == '\n')
			ResetParLine ();
		else
			++oLen;
		if (c != ' ')
			breakOK = 0;
	}
}


/*
	Process a string to apply indirection.
	Level	Action
	0	\ -> \
	1	\ -> \\
	2	\ -> \\\\

	Note: returns pointer into static buffer.
*/

static char *ApplyIndirection (s)
char	*s;
{
static char	buf[100];
static char	*p, c;
static int	slashCount, i;

	slashCount = 1;				/* figure out how many \'s */
	for (i = 0; i < indirectionLevel; i++)	/* one \ maps to */
		slashCount += slashCount;
	p = buf;
	while ((c = *s++) != '\0')
	{
		if (c != '\\')
			*p++ = c;
		else for (i = 0; i < slashCount; i++)
			*p++ = '\\';
	}
	*p = '\0';
	return (buf);
}


/*
	Draw horizontal line.  Sets vertical size not to space down very
	much, then restores.  Sets point size big for thick lines, then
	restores.

	Probably should take current boldface setting into account.
*/

static void DrawLine (type)
int	type;
{
int	ps;
double	vs;
char	buf[100], c;

	switch (type)
	{
	default:
	case rtfBorderHair:
	case rtfBorderSingle:
		ps = 10;
		vs = .1;
		c = '_';
		break;
	case rtfBorderThick:
	case rtfBorderShadow:
		ps = 36;
		vs = .3;
		c = '_';
		break;
	case rtfBorderDouble:
		ps = 5;
		vs = .3;
		c = '=';
		break;
	case rtfBorderDot:
		ps = 10;
		vs = .1;
		c = '.';
		break;
	}
	Flush ();
	if (ps != wcs->fontSize)	/* change point size if necessary */
		fprintf (f, ".ps %d\n", ps);
	fprintf (f, ".vs %gi\n", vs);
	sprintf (buf, "\\l'%gi\\&%c'", LineLen (ids, ips), c);
	fprintf (f, "%s\n", ApplyIndirection (buf));
	fprintf (f, ".br\n");
	fprintf (f, ".vs\n");		/* restore */
	if (ps != wcs->fontSize)	/* restore if was changed */
		fprintf (f, ".ps\n");
}


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

/*
	Miscellaneous stuff
*/


static char *TabTypeStr (type)
int	type;
{
char	*p = "";	/* assume left justified (default) */

	switch (type)
	{
	case rtfTabDecimal:	/* <- act like right tab, oh, well... */
	case rtfTabRight:	p = "R"; break;
	case rtfTabCenter:	p = "C"; break;
	}
	return (p);
}


static char *JustTypeStr (type)
int	type;
{
char	*p = "l";	/* default if unrecognized */

	switch (type)
	{
	default:		/* <- if unrecognized */
	case rtfQuadLeft:
		p = "l";
		break;
	case rtfQuadRight:
		p = "r";
		break;
	case rtfQuadCenter:
		p = "c";
		break;
	case rtfQuadJust:
		p = "b";
		break;
	}
	return (p);
}


/*
	Check vertical margins.  Constraints:
	
	Top margin should not extend to or below bottom margin
	Top margin should be below header margin
	Bottom margin MUST be above top margin (or Trap Loop Death will occur)
*/

static void CheckVMargins ()
{
	if (ids->topMargin + ids->bottomMargin >= ids->pageHeight)
	{
		fprintf (stderr, "Top margin is below bottom margin. Yow!\n");
		exit (1);
	}
}


static double LineLen (docState, parState)
DocState	*docState;
ParState	*parState;
{
	return (docState->pageWidth
			- (docState->leftMargin + docState->rightMargin)
			- parState->rightIndent);
}


/*
	Comment - dump a comment to the output.  The .\" and \n at
	beginning and end are supplied automatically.
*/


# ifdef	VARARGS

/*
	This version is for systems that have varargs.
*/

void
Comment (va_alist)
va_dcl
{
va_list	args;
char	*fmt;

	Flush ();
	fprintf (f, ".\\\" ");
	va_start (args);
	fmt = va_arg (args, char *);
	vfprintf (f, fmt, args);
	va_end (args);
	fprintf (f, "\n");
}

# else	/* !VARARGS */

/*
	This version is for systems that don't have varargs.
*/

void
Comment (fmt, a1, a2, a3, a4, a5, a6, a7, a8, a9)
char	*fmt;
char	*a1, *a2, *a3, *a4, *a5, *a6, *a7, *a8, *a9;
{
	Flush ();
	fprintf (f, ".\\\" ");
	fprintf (f, fmt, a1, a2, a3, a4, a5, a6, a7, a8, a9);
	fprintf (f, "\n");
}

# endif	/* VARARGS */
