/*
 * - Write } at end only if output is non-empty.
 * - Wrapper needs function to call during loop and termination value
 * to look for.
 * - Borders, esp. for spanned cells.
 * - Row-length lines break the table.  Do something better.
 * - If can assume that merges can only be rectangular, can dispense with
 * horiz merge controls.
 */

/*
 * There needs to be a kludge option to turn off some stuff, like
 * WordPerfect's failure to understand \sl.  (Some readers are dumb
 * and don't understand \lquote, \lsglquote, etc., so the hex
 * equivalents for the appropriate character set are written instead.)
 *
 * tc2rtf - read troffcvt output, convert to RTF
 *
 * Paragraph format values when \setup-end is seen are saved and
 * made the default style.  During further processing, paragraph
 * format changes are made to an internally cached state.  When it
 * comes time to write the first char of paragraph text, and differences
 * between the internal state and last written state are flushed out
 * to sync the internal and written states.
 *
 * (This implicitly means that para format changes WITHIN a paragraph
 * don't take effect until the next paragraph).
 *
 * 20 Apr 92	Paul DuBois	dubois@primate.wisc.edu
 *
 * 29 Apr 92 V1.00.  Created.
 */

#include	<stdio.h>
#include	<ctype.h>
#ifndef	STRING_H
#define	STRING_H <string.h>
#endif
#include	STRING_H

#include	"portlib.h"
#include	"etm.h"
#include	"memmgr.h"

#include	"tcgen.h"
#include	"tcunix.h"
#include	"tcread.h"
#include	"tctbl.h"

#include	"tc2rtf.h"


#define	twips	1440

#define	Twips(n)	((long) (((double) (n) * twips) / (resolution)))


/*
 * Minimum column width = 1 inch.
 * Setting it narrower (e.g., .5 inch) seemed to cause Word some problems.
 */
#define minColWidth	1440


static void	WriterInit (void);
static int	BeginFile (void);

static void	ReadFile (void);
static void	ControlLine (void);
static void	Prolog (void);
static void	WriteParFmt (ParFmt *bpf, ParFmt *npf);
static void	SetFont (char *name);
static void	SetPointSize (long size);
static void	SetTempIndent (long val);
static void	SetMargins (long nOffset, long nIndent, long nLineLen);
static void	SetSpacing (long nSpacing, long nLineSpacing);
static void	PlainText (void);
static void	SpecialText (void);
static void	Para (void);
static void	DrawLine (long length, int direction);
static void	Motion (long motion, int direction);
static void	ResetTabs (void);
static void	SetTabStop (long pos, char type);
static long	StrToTwips (char *s);
static long	StrToHalfPoints (char *s);
static void	ControlOut (char *p);
static void	TextStrOut (char *p);
static void	TextChOut (int c);
static void	StrOut (char *p);
static void	ChOut (char c);			/* int c? */
static int	AllowOutput (int yesno);

static void	TableBegin (short argc, char **argv);
static void	TableEnd (void);
static void	TableColumnInfo (short argc, char **argv);
static void	TableRowBegin (short argc, char **argv);
static void	TableRowEnd (void);
static void	TableRowLine (short argc, char **argv);
static void	TableCellInfo (short argc, char **argv);
static void	TableCellBegin (short argc, char **argv);
static void	TableCellEnd (void);
static void	TableEmptyCell (void);
static void	TableSpannedCell (void);
static void	TableCellLine (short argc, char **argv);
static void	CellBegin (void);
static void	CellEnd (void);

static int	oAllow = 1;	/* zero if output is throttled */


static int	pCount = 0;	/* text chars written to current paragraph */
static int	lCount = 0;	/* text chars written to current output line */

int	charSet = macCharSet;	/* default character set number */
char	*charSetName = "mac";	/* default character set name */



FTabInfo	*fTabList = (FTabInfo *) NULL;
SpChar	*spList = (SpChar *) NULL;


/*
 * Four paragraph format states are maintained.
 *
 * rtfParFmt	The standard RTF defaults.  Reflects the para
 * 		state after \pard is done.  Never modified.
 * intParFmt	Internal para state.  Para state changes are cached
 * 		between paras and flushed when the first text character
 * 		for a para needs to be written.
 * wrtParFmt	Para state as last written to the output.
 * tblParFmt	Used to save intParFmt during table processing.
 */

static ParFmt	rtfParFmt =
{
	(long)	tcrAdjLeft,		/* justification */
	(long)	0,			/* left indent */
	(long)	0,			/* right indent */
	(long)	0,			/* first indent */
	(long)	0,			/* space before paragraph */
	(long)	0,			/* space after paragraph */
	(long)	1000,			/* space between lines (automatic) */
	(int)	0,			/* number of tabstops */
	{ (long) 0 },			/* tabstop positions */
	{ (char) 'l' }			/* tabstop types */
};


static ParFmt	intParFmt;
static ParFmt	wrtParFmt;
static ParFmt	tblParFmt;


/*
	Convenience pointers into state structures
*/

ParFmt	*rpf = &rtfParFmt;
ParFmt	*ipf = &intParFmt;
ParFmt	*wpf = &wrtParFmt;
ParFmt	*tpf = &tblParFmt;

int	debug = 0;
int	echo = 0;

/*
 * Set on a per-file basis
 */

static long	resolution = 1440;

/* troff values */
static long	offset = 1 * twips;
static long	indent = 0;
/*static long	tempIndent = 0;*/
static long	lineLen = 9360;		/* 6.5 * twips */
static long	spacing = 12 * 2;	/* half-points */
static long	lineSpacing = 1;
static long    spaceSize = 12;	/* 12/36 em */

/* document format properties */

static long	margl = 0;	/* left/right margins fixed in Prolog() */
static long	margr = 0;
static long	pageLen = 11 * twips;
static long	pageWid = 12240;	/* 8.5 * twips */

/* character format properties */
/*static long	fontNum = 1;*/
static long	pointSize = 10 * 2;	/* size is in half-points */

static TCRFont	*curFInfo = (TCRFont *) NULL;
static char	curFont[30] = "R";
static long	charUpDn = 0;	/* super/subscript value, in half points */

/*
 * Per-table values
 */

static short	tblCols = 0;		/* number of columns in table */
static short	tblRows = 0;		/* number of rows in table */
static short	centerTbl= 0;		/* non-zero if table is centered */

/*
 * Per-column values
 *
 * The width and right-edge positions are maintained on a per-column
 * basis.  There is only a single column separation value, even though tbl
 * allows column separation to be set on a per-column basis.  That's
 * because RTF allows only a per-row value.  The separation value for the
 * first column is used.
 */

static long	colWidth[tblMaxCol];	/* column width */
static long	colRtEdge[tblMaxCol];	/* column right edge */
static short	colEqWidth[tblMaxCol];	/* column is equal-width? */
static long	colSep;			/* column separation */

/*
 * Per-cell values.  For the span stuff, the flag is set when in a span
 * (a set of > 1 cells that are merged together), the size is the number
 * of cells that the span comprises, and the idx is the index of the current
 * cell within the span (range is 0..size-1).
 */

static char	cellType[tblMaxCol];		/* cell type */
static short	cellVSpanFlag[tblMaxCol];	/* cell vspan flag */
static short	cellVSpanSize[tblMaxCol];	/* cell vspan extent */
static short	cellVSpanIdx[tblMaxCol];	/* cell vspan counter */
static short	cellHSpan[tblMaxCol];		/* cell hspan extent */
static short	cellHSpanFlag;			/* cell hspan flag */
static short	cellHSpanSize;			/* cell hspan extent */
static short	cellHSpanIdx;			/* cell hspan counter */
static char	cellVAdjust[tblMaxCol];		/* cell vertical adjust */
static short	cellBorder[tblMaxCol];		/* cell borders */
static short	inTbl = 0;

/*
 * Counters for keeping track of position within table.
 * tblColIdx counts horizontally during processing of \table-column-info
 * lines, during \table-cell-info lines, and while processing each row of
 * table data.  tblRowIdx counts rows of table data.
 */

static short	tblColIdx = 0;
static short	tblRowIdx = 0;



int
main (int argc, char *argv[])
{
char	buf[bufSiz];
int	inc;

	ETMInit (NULL);

	UnixSetProgPath (argv[0]);
	TCRSetOpenLibFileProc (UnixOpenLibFile);

	--argc;
	++argv;
	while (argc > 0 && argv[0][0] == '-')
	{
		inc = 1;
		if (strcmp (argv[0], "-D") == 0)
			debug = 0xffff;
		else if (strcmp (argv[0], "-E") == 0)
			echo = 1;
		else if (strcmp (argv[0], "-S") == 0)
		{
			if (argc < 2)
				ETMPanic ("Missing argument after -S");
			inc = 2;
			if (strcmp (argv[1], "ansi") == 0)
			{
				charSetName = argv[1];
				charSet = ansiCharSet;
			}
			else if (strcmp (argv[1], "mac") == 0)
			{
				charSetName = argv[1];
				charSet = macCharSet;
			}
			else if (strcmp (argv[1], "pc") == 0)
			{
				charSetName = argv[1];
				charSet = pcCharSet;
			}
			else if (strcmp (argv[1], "pca") == 0)
			{
				charSetName = argv[1];
				charSet = pcaCharSet;
			}
			else
				ETMPanic ("Unknown character set: %s", argv[1]);
		}
		else
			ETMPanic ("Unknown option: %s", argv[0]);
		argc -= inc;
		argv += inc;
	}

	WriterInit ();

	if (argc == 0)		/* stdin */
		ReadFile ();
	else while (argc > 0)
	{
		if (freopen (argv[0], "r", stdin) == (FILE *) NULL)
			ETMMsg ("Cannot open: %s", argv[0]);
		else
			ReadFile ();
		--argc;
		++argv;
	}

	StrOut ("}\n");		/* should only do if non-empty output! */

	ETMEnd ();
	exit (0);
	/*NOTREACHED*/
}


static void
WriterInit (void)
{
	if (!TCRReadFonts ("tcr-fonts"))
		ETMPanic ("cannot find tcr-fonts file");
	if (!ReadFontInfo ("rtf-fonts"))
		ETMPanic ("cannot find rtf-fonts file");
}


static int
BeginFile (void)
{
char	buf[bufSiz];

	sprintf (buf, "rtf-spec-%s", charSetName);
	if (!ReadSpecials (buf))
		ETMPanic ("cannot find %s file", buf);

	(void) AllowOutput (0);

	*wpf = *ipf = *rpf;	/* sync all states to RTF defaults */

	TCRInit ();

	return (1);
}


static void
ReadFile (void)
{
int	i;

	(void) BeginFile ();

	while (TCRGetToken () != tcrEOF)
	{
		if (echo)
		{
			ETMMsg ("class %d maj %d min %d <%s>",
				tcrClass, tcrMajor, tcrMinor, tcrArgv[0]);
			if (tcrClass == tcrControl || tcrClass == tcrSText)
			{
				for (i = 1; i < tcrArgc; i++)
					ETMMsg ("\t<%s>", tcrArgv[i]);
			}
		}
		switch (tcrClass)
		{
		case tcrControl:	ControlLine (); break;
		case tcrText:		PlainText (); break;
		case tcrSText:		SpecialText (); break;
		default:	ETMPanic ("ReadFile: unknown token %d <%s>",
							tcrClass, tcrArgv[0]);
		}
	}
}



static void
ControlLine (void)
{
char	*argv1 = tcrArgv[1];
char	buf[bufSiz];

	switch (tcrMajor)
	{
	default:
		ETMMsg ("ControlLine: bad control major code: %d <%s>",
						tcrMajor, tcrArgv[0]);
		break;
	case tcrCUnknown:
		ETMMsg ("ControlLine: unknown control token: <%s>",
						tcrArgv[0]);
		break;
	case tcrComment:
		break;
	case tcrSetupBegin:
		break;
	case tcrResolution:
		resolution = StrToLong (argv1);
		break;
	case tcrSetupEnd:
		(void) AllowOutput (1);
		Prolog ();
		break;
	case tcrPass:
		break;
	case tcrInputLine:
		break;
	case tcrOther:
		break;
	case tcrBreak:
		Para ();
		break;
	case tcrCFA:
		ipf->pJustify = tcrMinor;
		break;
	case tcrPageLength:
		break;
	case tcrPageNumber:
		break;
	case tcrFont:
		if (tcrArgc < 2)
			ETMMsg ("missing font on \\font line");
		else
			SetFont (tcrArgv[1]);
		break;
	case tcrPointSize:
		SetPointSize (StrToHalfPoints (argv1));
		break;
	case tcrSpacing:
		SetSpacing (StrToTwips (argv1), lineSpacing);
		break;
	case tcrLineSpacing:
		SetSpacing (spacing, StrToLong (argv1));
		break;
	case tcrOffset:
		SetMargins (StrToTwips (argv1), indent, lineLen);
		break;
	case tcrIndent:
		SetMargins (offset, StrToTwips (argv1), lineLen);
		break;
	case tcrTempIndent:
		SetTempIndent (StrToTwips (argv1));
		break;
	case tcrLineLength:
		SetMargins (offset, indent, StrToTwips (argv1));
		break;
	case tcrTitleLength:
		break;
	case tcrTitleBegin:
		break;
	case tcrTitleEnd:
		break;
	case tcrSpace:
		/*
		 * Begin new group and space to avoid changing params.
		 * Negative param means exact spacing (positive means
		 * "at least" spacing.
		 */
		if (pCount > 0)
			Para ();
		sprintf (buf, "{\\sl%ld\\sa0\\sb0\\par}\n", - StrToTwips (argv1));
		StrOut (buf);
		break;
	case tcrUnderline:
		break;
	case tcrCUnderline:
		break;
	case tcrNoUnderline:
		break;
	case tcrULineFont:
		break;
	case tcrBracketBegin:
		TextStrOut ("<BRACKET<");	/* ugly but makes it visible */
		break;
	case tcrBracketEnd:
		TextStrOut (">BRACKET>");
		break;
	case tcrBreakSpread:
		break;
	case tcrExtraSpace:
		break;
	case tcrLine:
		DrawLine (StrToTwips (argv1), tcrArgv[2][0]);
		break;
	case tcrMark:
		break;
	case tcrMotion:
		Motion (StrToTwips (argv1), tcrArgv[2][0]);
		break;
	case tcrBeginOverstrike:
		TextStrOut ("<OVERSTRIKE<");	/* ugly but makes it visible */
		break;
	case tcrOverstrikeEnd:
		TextStrOut (">OVERSTRIKE>");
		break;
	case tcrBeginPage:
		Para ();			/* necessary? */
		ControlOut ("page\n");
		break;
	case tcrZeroWidth:
		break;
	case tcrSpaceSize:
		spaceSize = StrToLong (argv1);
		break;
	case tcrConstantWidth:
		break;
	case tcrNoConstantWidth:
		break;
	case tcrNeed:
		break;
	case tcrEmbolden:
		break;
	case tcrSEmbolden:
		break;
	case tcrResetTabs:
		ResetTabs ();
		break;
	case tcrFirstTab:
		ResetTabs ();
		/* fall through... */
	case tcrNextTab:
		SetTabStop (StrToTwips (tcrArgv[1]), tcrArgv[2][0]);
		break;
	case tcrHyphenate:
		break;
	case tcrDiversionBegin:
		break;
	case tcrDiversionAppend:
		break;
	case tcrDiversionEnd:
		break;
	case tcrTabChar:
		break;
	case tcrLeaderChar:
		break;
	case tcrTableBegin:
		TableBegin (tcrArgc - 1, &tcrArgv[1]);
		break;
	case tcrTableEnd:
		TableEnd ();
		break;
	case tcrColumnInfo:
		TableColumnInfo (tcrArgc - 1, &tcrArgv[1]);
		break;
	case tcrRowBegin:
		TableRowBegin (tcrArgc - 1, &tcrArgv[1]);
		break;
	case tcrRowEnd:
		TableRowEnd ();
		break;
	case tcrRowLine:
		TableRowLine (tcrArgc - 1, &tcrArgv[1]);
		break;
	case tcrCellInfo:
		TableCellInfo (tcrArgc - 1, &tcrArgv[1]);
		break;
	case tcrCellBegin:
		TableCellBegin (tcrArgc - 1, &tcrArgv[1]);
		break;
	case tcrCellEnd:
		TableCellEnd ();
		break;
	case tcrCellLine:
		TableCellLine (tcrArgc - 1, &tcrArgv[1]);
		break;
	case tcrEmptyCell:
		TableEmptyCell ();
		break;
	case tcrSpannedCell:
		TableSpannedCell ();
		break;
	}
}


/*
 * The troffcvt setup section has now been seen, can write RTF prolog.
 *
 * margl and margr become the left and right margin values.  Those
 * can only be set once (since they're document properties).  They
 * become the zero-indent values.  Changes to offset, indent or
 * line length afterwards map onto changes in \li or \ri (which
 * are paragraph properties).
 */

static void
Prolog (void)
{
FTabInfo	*fp;
char	buf[bufSiz];

	margl = offset;
	margr = pageWid - offset - lineLen;
	SetMargins (offset, indent, lineLen);

	sprintf (buf, "{\\rtf1\\%s\\deff1\n", charSetName);
	StrOut (buf);
	StrOut ("{\\fonttbl\n");
	for (fp = fTabList; fp != (FTabInfo *) NULL; fp = fp->nextFTabInfo)
	{
		sprintf (buf, "{\\f%d\\fnil %s;}\n",
				fp->tcrInfo->tcrFontNum,
				fp->rtfName);
		StrOut (buf);
	}
	StrOut ("}\n");

	sprintf (buf, "\\paperh%ld \\paperw%ld\n", pageLen, pageWid);
	StrOut (buf);
	/*StrOut ("\\sectd\\sbknone\\linemod0\\linex0\\cols1\\endnhere\n");*/
	sprintf (buf, "margl%ld ", margl);
	ControlOut (buf);
	sprintf (buf, "margr%ld ", margr);
	ControlOut (buf);
	ControlOut ("pard ");
	/*WriteParFmt (rpf, ipf);*/
	/*
	 * Act like the para state written to the output is the
	 * same as the default state (because at the beginning of the
	 * document, it is).
	 */
	*wpf = *rpf;

	/* flush out initial character format values */

	SetFont (curFont);
	SetPointSize (pointSize);
}


/*
 * Draw a line (horizontal only).  The implicit assumption is that
 * "-" is as wide as a space (all guesses are arbitrary since RTF has
 * no font metrics).
 */

static void
DrawLine (long length, int direction)
{
long	ssTwips;		/* current space size in twips */

	if (direction != 'h')	/* ignore vertical lines */
		return;
	ssTwips = (spaceSize * (pointSize * 10)) / 36;
	while (length > 0)
	{
		TextStrOut ("-");
		length -= ssTwips;
	}
}


/*
 * Perform motion.  For horizontal motion, the implicit assumption
 * is that \~ and backspace are as wide as a space (all guesses are
 * arbitrary since RTF has no font metrics).
 *
 * Vertical motion is in half-points.  If there is any outstanding undone
 * vertical motion, it must be undone first to balance; some RTF readers
 * gag otherwise.
 */

static void
Motion (long motion, int direction)
{
char	buf[bufSiz];
long	ssTwips;	/* current space size in twips */

	if (direction == 'h')
	{
		ssTwips = (spaceSize * (pointSize * 10)) / 36;
		if (motion > 0)
		{
			while (motion > 0)
			{
				ControlOut ("~");
				motion -= ssTwips;
			}
		}
		else
		{
			while (motion < 0)
			{
				TextStrOut ("\b");
				motion += ssTwips;
			}
		}
	}
	else if (direction == 'v')
	{
		/* convert value to half-points (20 twips/point)*/
		motion /= 10;

		/* check for previous undone motion */

		if (charUpDn != 0)
		{
			if (charUpDn < 0)
				ControlOut ("up0 ");
			else
				ControlOut ("dn0 ");
			/*
				Factor out just-undone motion from request.
				If this leaves motion=0, nothing more need
				be done.
			*/

			motion += charUpDn;
			charUpDn = 0;
		}
		if (motion == 0)
			return;
		else if (motion < 0)
			sprintf (buf, "up%ld ", -motion);
		else
			sprintf (buf, "dn%ld ", motion);
		ControlOut (buf);
		charUpDn = motion;
	}
}


static void
ResetTabs (void)
{
	ipf->pTabCount = 0;
}


static void
SetTabStop (long pos, char type)
{
	if (ipf->pTabCount < maxTabStops)
	{
		/* convert position to be relative to left indent */
		pos += (offset + indent) - margl;
		ipf->pTabPos[ipf->pTabCount] = pos;
		ipf->pTabType[ipf->pTabCount] = type;
		++ipf->pTabCount;
	}
}


/*
 * Map troff combination of offset, indent and linelength onto the
 * RTF equivalents of left and right indent (which are themselves
 * relative to the left and right margin).
 */

static void
SetMargins (long nOffset, long nIndent, long nLineLen)
{
	offset = nOffset;
	indent = nIndent;
	lineLen = nLineLen;
	ipf->pLIndent = (offset + indent) - margl;
	ipf->pRIndent = (pageWid - offset - lineLen) - margr;
}


/*
 * Map troff temporary indent onto RTF first-line indent.  Also,
 * whereas troffcvt \temp-indent is an absolute value, RTF \fi
 * is relative to \li.
 */

static void
SetTempIndent (long val)
{
	ipf->pFIndent = val - indent;
}


/*
 * Set line spacing, which is a combination of the \spacing and
 * \line-spacing values.  This translates into RTF space-between
 * and space-after.
 */

static void
SetSpacing (long nSpacing, long nLineSpacing)
{
	spacing = nSpacing;
	lineSpacing = nLineSpacing;
	ipf->pSpaceBetween = ipf->pSpaceAfter = spacing * lineSpacing;
}


/*
 * This generates more output than it needs to, but it
 * basically works, so too bad.
 */

static void
SetFont (char *name)
{
TCRFont	*fp;
char	buf[bufSiz];

	if ((fp = TCRLookupFont (name)) == (TCRFont *) NULL)
	{
		ETMMsg ("no info for font <%s>", name);
		ControlOut ("f1\n");	/* default font */
		return;
	}
	sprintf (buf, "\\f%d\\i0\\b0\n", fp->tcrFontNum);
	StrOut (buf);
	if (fp->tcrAtts & tcrItal)
		ControlOut ("i ");
	if (fp->tcrAtts & tcrBold)
		ControlOut ("b ");
	curFInfo = fp;
	(void) strcpy (curFont, name);
}


/*
 * Size is maintained in half-points.
 */

static void
SetPointSize (long size)
{
char	buf[bufSiz];

	pointSize = size;
	sprintf (buf, "fs%ld ", pointSize);
	ControlOut (buf);
}


static void
PlainText (void)
{
	if (tcrMajor != '\n')
	{
		if (tcrMajor == '{' || tcrMajor == '}')
			TextChOut ('\\');
		TextChOut (tcrMajor);
	}
}


static void
SpecialText (void)
{
SpChar	*sp;
char	buf[bufSiz];
int	setFont = 0;
char	font[bufSiz];
TCRFont	*fntInfo;

	if ((sp = LookupSpecial (&tcrArgv[0][1])) == (SpChar *) NULL
		|| sp->spValue == (char *) NULL)
	{
		sprintf (buf, "[[%s]]", &tcrArgv[0][1]);
		TextStrOut (buf);
	}
	else
	{
		setFont = 0;
		if (sp->spFont != NULL && strcmp (sp->spFont, curFont) != 0)
			++setFont;

		if (setFont)
		{
			StrOut ("{");
			/* save info so can restore manually */
			(void) strcpy (font, curFont);
			fntInfo = curFInfo;
			SetFont (sp->spFont);
		}
		TextStrOut (sp->spValue);
		if (setFont)
		{
			StrOut ("}");
			(void) strcpy (curFont, font);
			curFInfo = fntInfo;
		}
	}
}


static void
Para (void)
{
	ControlOut ("par\n");
	SetTempIndent (indent);
	pCount = 0;
	lCount = 0;
}


/*
 * Write out paragraph format properties that differ from some
 * baseline.  bpf = base paragraph format, npf = new paragraph
 * format.
 */

static void
WriteParFmt (ParFmt *bpf, ParFmt *npf)
{
char	buf[bufSiz];
char	*p;
int	c, i;

	if (bpf->pJustify != npf->pJustify)
	{
		p = (char *) NULL;

		switch (npf->pJustify)
		{
		default:
			ETMPanic ("WriteParFmt: bad justify code %d",
							npf->pJustify);
		case tcrCenter:
		case tcrAdjCenter:
			p = "qc ";
			break;
		case tcrNofill:
		case tcrAdjLeft:
			p = "ql ";
			break;
		case tcrAdjFull:
			p = "qj ";
			break;
		case tcrAdjRight:
			p = "qr ";
			break;
		}
		ControlOut (p);
	}
	if (bpf->pLIndent != npf->pLIndent)
	{
		sprintf (buf, "li%ld ", npf->pLIndent);
		ControlOut (buf);
	}
	if (bpf->pRIndent != npf->pRIndent)
	{
		sprintf (buf, "ri%ld ", npf->pRIndent);
		ControlOut (buf);
	}
	if (bpf->pFIndent != npf->pFIndent)
	{
		sprintf (buf, "fi%ld ", npf->pFIndent);
		ControlOut (buf);
	}
	if (bpf->pSpaceBetween != npf->pSpaceBetween)
	{
		sprintf (buf, "sl%ld ", npf->pSpaceBetween);
		ControlOut (buf);
	}
/*
	Output looks better without this...

	if (bpf->pSpaceAfter != npf->pSpaceAfter)
	{
		sprintf (buf, "sa%ld ", npf->pSpaceAfter);
		ControlOut (buf);
	}
*/

	/* if there are no explicit tabs, this writes nothing */
	if (bpf->pTabCount != npf->pTabCount)
	{
		if (npf->pTabCount > 0)
			ChOut ('\n');
		for (i = 0; i < npf->pTabCount; i++)
		{
			sprintf (buf, "tx%ld ", npf->pTabPos[i]);
			ControlOut (buf);
			if ((c = npf->pTabType[i]) == 'l')	/* default */
				continue;
			sprintf (buf, "tq%c ", c == 'c' ? 'c' : 'r');
			ControlOut (buf);
		}
		if (npf->pTabCount > 0)
			ChOut ('\n');
	}
}


/*
 * basic output routines
 *
 * ControlOut() - write control word.
 * TextStrOut() - write out a string of paragraph text.
 * TextChOut() - write out a character of paragraph text.
 * StrOut() - write out a string.
 * ChOut() - write out a character.  ALL output (except ETM messages)
 * 	comes through this routine.
 * AllowOutput () - allow or throttle output.
 */


static void
ControlOut (char *p)
{
	if (p != (char *) NULL)
	{
		if (lCount > 0)
		{
			ChOut ('\n');
			lCount = 0;
		}
		ChOut ('\\');
		StrOut (p);
	}
}



static void
TextStrOut (char *p)
{
	if (p != (char *) NULL)
	{
		while (*p != '\0')
			TextChOut (*p++);
	}
}


/*
 * Write a character of paragraph text.  If this is the first character
 * and the internally maintained paragraph format state is different
 * than the last one written out, put them in sync:  write \pard to
 * reset to RTF defaults.  If the internal state is then different
 * from that, write output to put it into effect.
 */
	
static void
TextChOut (int c)
{
	if (pCount == 0 && !ParFmtEq (wpf, ipf))
	{
		ControlOut ("pard ");
		if (inTbl)
			ControlOut ("intbl ");
		*wpf = *rpf;
		if (!ParFmtEq (wpf, ipf))
		{
			WriteParFmt (wpf, ipf);
			*wpf = *ipf;
		}
	}
	ChOut (c);
	++pCount;
	/* break lines every now and then */
	if (++lCount > 50 && c == ' ')
	{
		ChOut ('\n');
		lCount = 0;
	}
}



static void
StrOut (char *p)
{
	if (p != (char *) NULL)
	{
		while (*p != '\0')
			ChOut (*p++);
	}
}


static void
ChOut (char c)
{
	if (oAllow && putc (c, stdout) == EOF)
		ETMPanic ("Write error, cannot continue");
}


static int
AllowOutput (int yesno)
{
int	prev = oAllow;

	oAllow = yesno;
	return (prev);
}


/*
 * Convert a string representing basic units to twips
 */

static long
StrToTwips (char *s)
{
	return (Twips (StrToLong (s)));
}


/*
 * Convert a string representing points to half-points.
 */

static long
StrToHalfPoints (char *s)
{
	return (StrToLong (s) * 2);
}


/*
 * Table element routines
 *
 * Tables are written out in a group ({...}) to isolate formatting
 * changes made in the table from the surrounding part of the document.
 */

static void
TableBegin (short argc, char **argv)
{
	if (argc != 8)
		ETMPanic ("TableBegin: wrong number of args: %hd", argc);
	tblCols = StrToShort (argv[tblColsArg]);
	tblRows = StrToShort (argv[tblRowsArg]);

	if (tblCols == 0)
		ETMPanic ("TableBegin: zero column table");
	if (tblCols > tblMaxCol)
		ETMPanic ("TableBegin: too many table columns (%hd), max = %d",
						tblCols, tblMaxCol);

	/* determine whether or not to center the table */
	centerTbl = argv[tblAlignArg][0] == 'C'? 1 : 0;

	tblColIdx = 0;
	tblRowIdx = 0;

	inTbl = 1;

	/* put the table in a group */

	ChOut ('{');
	*tpf = *ipf;		/* save current internal state */
	*ipf = *rpf;		/* set internal state to default */
	WriteParFmt (wpf, ipf);	/* sync written state to new internal state */
}


static void
TableEnd (void)
{
	inTbl = 0;

	/* end the table group */

	*ipf = *tpf;		/* restore saved internal state */
	WriteParFmt (wpf, ipf);	/* sync written state to restored state */

	ChOut ('}');
}


static void
TableColumnInfo (short argc, char **argv)
{
long	width;
long	totWidth, defWidth;
long	rtEdge;
short	zeroes;
short	i;

	if (argc != 3)
		ETMPanic ("TableColumnInfo: wrong number of args: %hd", argc);
	if (tblColIdx >= tblMaxCol)
		ETMPanic ("TableColumnInfo: logic error");

	/*
	 * For separation and width, 0 means "none specified".  RTF allows
	 * only per-row separation, so use the first column separation value
	 * for the first cell in the row.
	 */
	if (tblColIdx == 0)
		colSep = StrToTwips (argv[colSepArg]);
	colWidth[tblColIdx] = StrToTwips (argv[colWidthArg]);

	++tblColIdx;
	if (tblColIdx < tblCols)
		return;

	/*
	 * Have gotten all column info now.  Figure out total width of columns
	 * that had a width specified and how many didn't have any width
	 * specified.  Make the unspecified columns all the same width (a
	 * hack, but I don't know how to figure out how wide they should be
	 * without a lot of messing around).  Do this by taking whatever's
	 * left of the line width after subtracting the width of the specified
	 * columns and dividing it among the unspecified columns.  But don't
	 * make them less than some minimum width.
	 *
	 * RTF column width values *include* the column separation, so the
	 * right edge values calculated below will include that.
	 */

	if (colSep < 0)
		colSep = 0;

	totWidth = 0;
	zeroes = 0;
	for (i = 0; i < tblCols; i++)
	{
		if (colWidth[i])
			totWidth += colWidth[i];
		else
			++zeroes;
	}
	defWidth = 0;
	if (totWidth - lineLen > 0 && zeroes > 0)
		defWidth = (totWidth - lineLen) / zeroes;
	if (defWidth < minColWidth)
		defWidth = minColWidth;

	/* now determine the right edge positions (including colSep) */

	rtEdge = 0;
	for (i = 0; i < tblCols; i++)
	{
		if (colWidth[i])
			colRtEdge[i] = rtEdge + colWidth[i] + colSep;
		else
			colRtEdge[i] = rtEdge + defWidth + colSep;
		rtEdge = colRtEdge[i];
	}
}


/*
 * Begin a table row.
 */

static void
TableRowBegin (short argc, char **argv)
{
	/*
	 * Reset tblColIdx for use by \table-cell-info controls
	 */
	tblColIdx = 0;
}


static void
TableRowEnd (void)
{
	if (tblColIdx != tblCols)	/* paranoia check */
	{
		ETMPanic ("columns in row %hd = %hd, should be %hd",
			tblRowIdx + 1, tblColIdx, tblCols);
	}
	ControlOut ("row ");
	ControlOut ("pard ");
	++tblRowIdx;		/* done with row, bump counter */
}


/*
 * A row of a table should be a table-width line.  Fake it by drawing
 * a short line in each column.
 */

static void
TableRowLine (short argc, char **argv)
{
short	i;

/*
 * Can't do this without writing the regular other table row stuff!
	for (i = 0; i < tblCols; i++)
		TableCellLine (0, (char **) NULL);
*/
	StrOut ("---");
	ControlOut ("par");
	++tblRowIdx;		/* done with row, bump counter */
}


static void
TableCellInfo (short argc, char **argv)
{
char	buf[bufSiz];
short	i;
short	vspan;

	if (argc != 5)
		ETMPanic ("TableCellInfo: wrong number of args: %hd", argc);

	cellType[tblColIdx] = argv[cellTypeArg][0];

	vspan = StrToShort (argv[cellVSpanArg]);
	if (vspan == 1)	/* not part of a span */
	{
		cellVSpanFlag[tblColIdx] = 0;
	}
	else if (vspan > 1)		/* part of a vspan -- first cell */
	{
		cellVSpanFlag[tblColIdx] = 1;
		cellVSpanSize[tblColIdx] = vspan;
		cellVSpanIdx[tblColIdx] = 0;
	}
	else				/* part of a vspan -- not first cell */
	{
		++cellVSpanIdx[tblColIdx];
	}

	cellHSpan[tblColIdx] = StrToShort (argv[cellHSpanArg]);
	cellVAdjust[tblColIdx] = argv[cellVAdjustArg][0];
	cellBorder[tblColIdx] = StrToShort (argv[cellBorderArg]);

	/*
	 * Bump cell index.
	 * Reset if we've reached the end, so it'll be correct for
	 * \table-cell-begin, \table-empty-cell, \table-spanned-cell,
	 * \table-cell-line.  Also write out all the \cellx things
	 * for the row since we now have all the necessary information.
	 */

	++tblColIdx;
	if (tblColIdx < tblCols)
		return;
	tblColIdx = 0;

	ControlOut ("trowd");
	sprintf (buf, "trgaph%ld", colSep / 2);
	ControlOut (buf);
	sprintf (buf, "trleft%ld", -colSep / 2);
	ControlOut (buf);
	ControlOut ("trkeep");
	for (i = 0; i < tblCols; i++)
	{
		if (cellHSpan[i] > 1)	/* first hspan cell */
			ControlOut ("clmgf");
		if (cellHSpan[i] == 0)
			ControlOut ("clmrg");	/* cell is hspanned */
		if (cellVSpanFlag[i])	/* part of a vspan */
		{
			if (cellVSpanIdx[i] == 0)	/* first vspan cell */
				ControlOut ("clvmgf");
			else
				ControlOut ("clvmrg");
		}
		sprintf (buf, "cellx%ld", colRtEdge[i]);
		ControlOut (buf);
	}
}


static void
TableCellBegin (short argc, char **argv)
{
	CellBegin ();
	switch (cellType[tblColIdx])
	{
	default:
		break;	/* don't do anything */
	case 'C':
		ControlOut ("qc");
		break;
	case 'R':
	case 'N':	/* treat numeric as right-aligned */
		ControlOut ("qr");
		break;
	}
}


static void
TableCellEnd (void)
{
	CellEnd ();
}


static void
TableEmptyCell (void)
{
	CellBegin ();
	CellEnd ();
}


static void
TableSpannedCell (void)
{
	CellBegin ();
	CellEnd ();
}


/*
 * This is really poor.  What's a better way?
 */

static void
TableCellLine (short argc, char **argv)
{
	CellBegin ();
	StrOut ("---");
	CellEnd ();
}



static void
CellBegin (void)
{
short	hspan;

	hspan = cellHSpan[tblColIdx];
	if (hspan == 1)			/* not part of a hspan */
	{
		cellHSpanFlag = 0;
	}
	else if (hspan > 1)		/* part of a hspan -- first cell */
	{
		cellHSpanFlag = 1;
		cellHSpanSize = hspan;
		cellHSpanIdx = 0;
	}
	else				/* part of a hspan -- not first cell */
	{
		++cellHSpanIdx;
	}

	/*
	 * Order is important here.  \pard following \intbl seems to make
	 * Word forget that it's in a cell.
	 */

	ControlOut ("pard ");
	ControlOut ("intbl ");
}


static void
CellEnd (void)
{
	ControlOut ("cell");
	pCount = 0;
	lCount = 0;
	++tblColIdx;
}
