/*
 * To do:
 * - .sp doesn't pay attention to argument.
 * - Add Latin-1 encoding?
 * - TableCellLine() doesn't handle spans. Nor does TableEmptyCell().
 * - Count unspecified-width cells, use line-length to improve guess.
 * - Why do I get a space before cell text?
 * - Maybe tblcvt could examine table data and make some guesses about
 * column widths...
 */
/*
 *
 * tc2text - read output from troffcvt and extract the text
 *
 * This troffcvt postprocessor is part of unroff - a smarter
 * version of deroff.  For one thing, it can be configured to
 * do different things with special characters simply by editing
 * text-specials.
 *
 * - Doesn't handle table centering.
 *
 * 20 Apr 92	Paul DuBois	dubois@primate.wisc.edu
 *
 * 20 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	"tokenscan.h"

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


#define	spCharFile	"text-specials"
#define	spChar8File	"text-8-specials"	/* 8-bit output characters */

#define	defResolution	432

#define	charsPerInch	12
#define	StrToChars(s)	((StrToLong(s) * charsPerInch) / resolution)

#define	maxTabStops	35

#define	defColWidth	12		/* 12 characters */
#define	defColSep	2		/* 2 characters */

/*
 * Adjustment types (there is no "adjust-both"; \adjust-full maps to adjLeft)
 */

#define	adjLeft		0	/* left justify */
#define	adjCenter	1	/* center */
#define	adjRight	2	/* right justify */


/*
 * Special character structure
 */


typedef	struct SpChar	SpChar;

struct SpChar
{
	char	*spName;
	unsigned char	*spValue;
	SpChar	*spNext;
};



static void	ReadFile (void);
static void	ControlLine (void);
static void	SpecialText (void);
static void	PlainText (void);
static void	ResetTabs (void);
static void	SetTabStop (long pos, char type);
static void	DrawLine (long length, int direction);
static int	ReadSpecials (char *filename);
static SpChar	*LookupSpecial (char *name);
static void	Break (void);
static void	Para (void);
static long	NextTabAmount (void);
static void	WrapLine (int trigger);
static void	FlushBuf (short addNewline);
static void	ChOut (int c);
static void	ChOutWrap (int c);
static void	ChOutNoWrap (int c);
static void	StrOut (char *s);

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	TabToTableCol (void);


static char	*usage = "tc2text [ -E ] [ -J ] [ -8 ] [ file ] ...";

static SpChar	*spList;

static int	split = 1;
static int	echo = 0;
static int	eightbit = 0;	/* whether or not to include 8-bit chars */


static long	resolution;

/*
 * Stuff for managing output buffer used for line filling and breaking
 *
 * oBuf		Output buffer
 * oPtr		Pointer into buffer (no terminating null is used)
 * oPos		Logical position into output line (even if buffer has gotten
 *		full and has been flushed)
 * indentAmt	Amount by which to indent current line when it gets flushed
 *
 * For best results, oBufSiz should be large (longer than any reasonable
 * expected output line length.  The program simply panics if the buffer
 * overflows.
 */

#define	oBufSiz		10240

static char	oBuf[oBufSiz];
static char	*oPtr;
static long	oPos;

static long	offset;
static long	indent;
static long	tmpIndent;
static long	indentAmt;
static long	lineLen;
static short	segment;

static short	adjust = adjLeft;	/* line adjustment mode */
static short	fill = 0;		/* non-zero if fill mode on */
static short	center = 0;		/* non-zero if centering on */

static int	tossWhite;

static short	tabPos[maxTabStops];
static char	tabType[maxTabStops];	/* this is actually ignored */
static short	tabCount = 0;


/*
 * Per-table values
 */

static short	tblCols = 0;		/* number of columns in table */
static short	tblRows = 0;		/* number of rows in table */
static short	tblWidth = 0;		/* width of table */

/*
 * Per-column values
 */

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

/*
 * 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 */

/*
 * 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	*p;
int	inc;

	ETMInit (NULL);

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

	--argc;
	++argv;
	while (argc > 0 && argv[0][0] == '-')
	{
		inc = 1;
		if (strcmp (argv[0], "-E") == 0)
			echo = 1;
		else if (strcmp (argv[0], "-J") == 0)
			split = 0;
		else if (strcmp (argv[0], "-8") == 0)
			eightbit = 1;
		else
		{
			ETMMsg ("Unknown option: %s", argv[0]);
			ETMPanic ("%s", usage);
		}

		argc -= inc;
		argv += inc;
	}

	/*
	 * Read special character definitions.  Then add to those the
	 * definitions for 8-bit Latin-1 characters if they were requested
	 * from the command line.
	 */

	if (!ReadSpecials (spCharFile))
		ETMPanic ("cannot find %s file", spCharFile);
	if (eightbit && !ReadSpecials (spChar8File))
		ETMPanic ("cannot find %s file", spChar8File);

	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;
	}

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


static int
BeginFile (void)
{
	resolution = defResolution;

	lineLen = (long) (6.5 * charsPerInch);
	offset = 0;	/* this isn't really used */

	oPtr = oBuf;
	oPos = 0;
	segment = 1;
	indentAmt = 0;
	tossWhite = 0;

	return (1);
}


static void
ReadFile (void)
{
int	i;

	TCRInit ();

	(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 ("    <%s>", tcrArgv[i]);
			}
		}
		switch (tcrClass)
		{
		case tcrControl:	ControlLine (); break;
		case tcrText:		PlainText (); break;
		case tcrSText:		SpecialText (); break;
		default:	ETMPanic ("ReadFile: unknown class %d",
							tcrClass);
		}
	}
}


static void
ControlLine (void)
{
	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 tcrBreak:
		Break ();
		break;
	case tcrSpace:		/* pay attention to argument? */
		Break ();
		ChOut ('\n');
		break;
	case tcrCFA:
		switch (tcrMinor)
		{
		case tcrNofill:
			fill = 0;
			break;
		case tcrCenter:
			center = 1;
			break;
		case tcrAdjFull:
		case tcrAdjLeft:
			center = 0;
			fill = 1;
			adjust = adjLeft;
			break;
		case tcrAdjRight:
			center = 0;
			fill = 1;
			adjust = adjRight;
			break;
		case tcrAdjCenter:
			center = 1;
			break;
		}
		break;
	case tcrComment:
		break;
	case tcrSetupBegin:
		break;
	case tcrSetupEnd:
		break;
	case tcrResolution:
		resolution = StrToLong (tcrArgv[1]);
		break;
	case tcrPass:
		break;
	case tcrInputLine:
		break;
	case tcrOther:
		break;
	case tcrFont:
		break;
	case tcrPointSize:
		break;
	case tcrSpacing:
		break;
	case tcrLineSpacing:
		break;
	case tcrOffset:
		offset = StrToChars (tcrArgv[1]);
		break;
	case tcrIndent:
		tmpIndent = indent = StrToChars (tcrArgv[1]);
		break;
	case tcrTempIndent:
		tmpIndent = StrToChars (tcrArgv[1]);
		break;
	case tcrLineLength:
		/*
		 * If the output line length goes above the length of the line
		 * collection buffer, there will be trouble.  Clip the length
		 * if necessary.
		 */
		lineLen = StrToChars (tcrArgv[1]);
		if (lineLen > sizeof (oBuf))
		{
			lineLen = sizeof (oBuf);
			ETMMsg ("unroff: line length clipped to %ld chars",
								lineLen);
		}
		break;
	case tcrPageLength:
		break;
	case tcrPageNumber:
		break;
	case tcrTitleLength:
		break;
	case tcrTitleBegin:
		break;
	case tcrTitleEnd:
		break;
	case tcrUnderline:
		break;
	case tcrCUnderline:
		break;
	case tcrNoUnderline:
		break;
	case tcrULineFont:
		break;
	case tcrBracketBegin:
		break;
	case tcrBracketEnd:
		break;
	case tcrBreakSpread:
		break;
	case tcrExtraSpace:
		break;
	case tcrLine:
		DrawLine (StrToLong (tcrArgv[1]), tcrArgv[2][0]);
		break;
	case tcrMark:
		break;
	case tcrMotion:
		break;
	case tcrBeginOverstrike:
		break;
	case tcrOverstrikeEnd:
		break;
	case tcrBeginPage:
		break;
	case tcrZeroWidth:
		break;
	case tcrSpaceSize:
		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 (StrToLong (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;
	}
}


static void
PlainText (void)
{
	if (tcrMajor != '\n' && tcrMajor != '\r')
		ChOut (tcrMajor);
}


/*
 * Draw a line (horizontal only).  The number of - characters to
 * write is just a guess appropriate for terminals; probably should
 * be a way of controlling with a command line argument.
 */

static void
DrawLine (long length, int direction)
{
	if (tcrArgv[2][0] != 'h')	/* ignore vertical lines */
		return;
	length = length * charsPerInch / resolution;
	while (length-- > 0)
		ChOut ('-');
}


static void
SpecialText (void)
{
SpChar	*sp;

	if ((sp = LookupSpecial (&tcrArgv[0][1])) == (SpChar *) NULL)
	{
		StrOut ("[[");
		StrOut (&tcrArgv[0][1]);
		StrOut ("]]");
	}
	else
		StrOut ((char *) sp->spValue);
}


/*
 * Read special character list.  Character codes may be specified numerically
 * as #nnn where nnn is the decimal value.  To specify a literal #, use ##.
 */

static int
ReadSpecials (char *filename)
{
TSScanner	scanner;
FILE	*f;
char	buf[bufSiz];
SpChar	*sp;
unsigned char	*name, *value;
unsigned char	*p, *p2, c;

	if ((f = TCROpenLibFile (filename, "r")) == (FILE *) NULL)
		return (0);
	TSGetScanner (&scanner);		/* want default scanner */
	TSSetScanner ((TSScanner *) NULL);	/* behavior */
	while (TCRGetLine (buf, (int) sizeof (buf), f))
	{
		if (buf[0] == '#')
			continue;
		TSScanInit (buf);
		if ((name = (unsigned char *) TSScan ()) == (unsigned char *) NULL)
			continue;
		if ((value = (unsigned char *) TSScan ()) == (unsigned char *) NULL)
			value = (unsigned char *) "";

		/*
		 * convert #nnn to a single char with value decimal nnn,
		 * #x (x anything else) to just x.
		 */
		p = p2 = value;
		while (*p2 != '\0')
		{
			if ((c = *p2++) == '#' && *p2 != '\0')
			{
				if (!isdigit (*p2) || !isdigit (*(p2+1))
						|| !isdigit (*(p2+2)))
				{
					c = *p2++;
				}
				else
				{
					c = (*p2 - '0') * 100
						+ (*(p2+1) - '0') * 10
						+ (*(p2+2) - '0');
					p2 += 3;
				}
			}
			*p++ = c;
		}
		*p = '\0';
		/*
		 * If char hasn't been defined yet, allocate a new structure
		 * for it; otherwise reuse the structure by deallocating
		 * the existing name and value fields.
		 */
		if ((sp = LookupSpecial ((char *) name)) == (SpChar *) NULL)
		{
			sp = New (SpChar);
			sp->spNext = spList;
			spList = sp;
		}
		else
		{
			Free (sp->spName);
			Free (sp->spValue);
		}
		sp->spName = StrAlloc (name);
		sp->spValue = (unsigned char *) StrAlloc ((char *) value);
	}
	(void) fclose (f);

	TSSetScanner (&scanner);
	return (1);
}


static SpChar *
LookupSpecial (char *name)
{
SpChar	*sp;

	for (sp = spList; sp != (SpChar *) NULL; sp = sp->spNext)
	{
		if (strcmp (name, sp->spName) == 0)
			break;
	}
	return (sp);	/* NULL if not found */
}


static void
ResetTabs (void)
{
	tabCount = 0;
}


static void
SetTabStop (long pos, char type)
{
	if (tabCount >= maxTabStops)
	{
		ETMMsg ("too many tabstops");
		return;
	}
	/*
	 * Assume charsPerInch characters/inch
	 */
	tabPos[tabCount] = (pos * charsPerInch) / resolution;
	tabType[tabCount] = type;
	++tabCount;
}


/*
 * Return the number of spaces to put out to reach the next tab stop, taking
 * into account the indent for the current output line.  Always returns a
 * value of at least 1.  If there are no tabstops past the current output
 * position, act as though there are tabs every 8 characters from that point
 * on.
 *
 * Tabs are calculated relative to the page offset, which is considered the
 * origin.
 */

static long
NextTabAmount (void)
{
int	i;
long	curPos;

	curPos = indentAmt + oPos;
	for (i = 0; i < tabCount; i++)
	{
		if (tabPos[i] > curPos)
			return (tabPos[i] - curPos);
	}
	return (8 - (curPos % 8));
}


static void
Break (void)
{
	if (oPos)		/* force out output line if there is one */
		Para ();
}


static void
Para (void)
{
	ChOut ('\n');
}


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


static void
ChOut (int c)
{
	if (split)
		ChOutWrap (c);
	else
		ChOutNoWrap (c);
}


/*
 * The normal case (no -J option) is to write characters and wrap lines
 * at the right border unless in no-fill mode.
 */

static void
ChOutWrap (int c)
{
long	advance;

	/* ignore whitespace after line wrapping to avoid extraneous indents */
	if (tossWhite && (c == ' ' || c == '\t'))
		return;
	/* not a whitespace character; can stop tossing white */
	tossWhite = 0;

	/*
	 * Before writing out first char of output line, calculate the indent
	 * to use when the line is finally forced out.  (This must be done
	 * BEFORE tab expansion.)
	 */
	if (oPos == 0)
		indentAmt = tmpIndent;

	/*
	 * End of line means we know we've seen the end of the current word.
	 * Check whether line needs wrapping and flush.
	 */

	if (c == '\n')			/* end of line */
	{
		if (fill && indentAmt + oPos >= lineLen)
			WrapLine ('\n');
		else
			FlushBuf (1);
		return;
	}

	if (c == '\t')			/* do tab expansion */
	{
		advance = NextTabAmount ();
		while (advance-- > 0)
			ChOut (' ');
		return;
	}
	
	if (oPtr >= oBuf + sizeof (oBuf))
	{
		FlushBuf (1);
		ETMPanic ("unroff: output buffer capacity exceeded");
	}

	/*
	 * If the character is a space and the output line has reached
	 * or exceeded the desired line length, wrap the line.  WrapLine()
	 * takes care of adding the space.
	 */

	if (c == ' ' && fill && indentAmt + oPos >= lineLen)
		WrapLine (' ');
	else
	{
		*oPtr++ = c;
		++oPos;		/* increment logical line position */
	}
}


/*
 * Write out characters, but don't do line wrapping at the right border.
 * This makes paragraphs appear in the output on a single line, which is
 * more appropriate when moving the resulting document to a word processor
 * that treats paragraphs internally as one line.
 */

static void
ChOutNoWrap (int c)
{
long	advance;

	/*
	 * Before writing out first char of output line, calculate the indent
	 * to use when the line is finally forced out.  (This must be done
	 * BEFORE tab expansion.)
	 */
	if (oPos == 0)
		indentAmt = tmpIndent;

	if (c == '\n')			/* end of line */
	{
		FlushBuf (1);
		return;
	}

	if (c == '\t')			/* do tab expansion */
	{
		advance = NextTabAmount ();
		while (advance-- > 0)
			ChOut (' ');
		return;
	}
	
	if (oPtr >= oBuf + sizeof (oBuf))
		FlushBuf (0);

	*oPtr++ = c;
	++oPos;		/* increment logical line position */
}


/*
 * Flush output line currently being collected, without line wrapping.
 * Since a paragraph may be a single line output line, the line may need
 * to be flushed in segments.
 *
 * addNewline is nonzero if a newline should be written after flushing
 * the buffer.  This will be zero if the buffer was discovered to have
 * filled up and another character needs to be added to it.  Always call
 * this routine with addNewline = 1 for the line-wrapping case.
 */

static void
FlushBuf (short addNewline)
{
char	*p;
long	i;

	/* can perform centering only if entire output line is in buffer */

	if (center && segment == 1 && addNewline)
	{
		i = (lineLen - oPos) / 2;
		while (i-- > 0)
			putchar (' ');
	}

	/* discard any trailing whitespace on the line (if last segment) */

	if (addNewline)
	{
		while (oPtr > oBuf && *(oPtr-1) == ' ')
			--oPtr;
	}
	if (oPtr > oBuf)		/* if line not empty, write contents */
	{
		if (segment == 1)	/* add indent only to first segment */
		{
			for (i = 0; i < indentAmt; i++)
				putchar (' ');
		}
		for (p = oBuf; p < oPtr; p++)
			putchar (*p);
	}

	/* write final newline */
	if (addNewline)
	{
		putchar ('\n');

		/*
		 * Reinitialize line-collection variables
		 */

		oPtr = oBuf;
		oPos = 0;
		tossWhite = 0;
		tmpIndent = indent;
		segment = 1;
	}
	else
	{
		oPtr = oBuf;
		++segment;
	}
}


/*
 * Handle line wrapping.  This occurs when the end of a word is found (which
 * is known by encountering whitespace or newline) and the output line length
 * reaches or exceeds the desired line length.  Flush out as many words from
 * the current buffer as will fit within the line length, then shove the
 * remainder to the beginning of the buffer; that remainder becomes the
 * beginning of the next output line.
 *
 * Don't call this routine in nofill mode
 *
 * trigger is the character that was seen that triggered the call to
 * WrapLine() (space, tab, or newline).  If the entire buffer is flushed,
 * the trigger is discarded.  Otherwise, after moving down any trailing part
 * of the buffer, the trigger is rerouted through ChOut().
 */

static void
WrapLine (int trigger)
{
char	*p, *oPtrSave;

	/*
	 * If line is exactly the desired length, flush it and discard
	 * the trigger character.
	 */

	if (indentAmt + oPos == lineLen)
	{
		FlushBuf (1);
		return;
	}

	/*
	 * Line is longer than desired length.  Split off the last word,
	 * flush the stuff preceding it, then move the word down to the
	 * beginning of the beginning of the buffer, and add the trigger
	 * character after the word.
	 * SPECIAL CASE: if the entire buffer is a single word, it can't
	 * be split.  Flush it all and discard the trigger, just as if the
	 * line were exactly the right length.
	 */

	p = oPtr;
	while (p > oBuf && *(p-1) != ' ')	/* seek last space */
		--p;
	if (p == oBuf)		/* entire buffer is one word! */
	{
		FlushBuf (1);
		return;
	}

	/*
	 * p points to beginning of final word.  Save value of oPtr for
	 * later, move it back to the beginning of the word and flush that
	 * much of the buffer.
	 */

	oPtrSave = oPtr;
	oPtr = p;
	FlushBuf (1);

	/*
	 * Now re-add the last word to the (beginning of the) buffer.  This
	 * is tricky, because we're adding characters to the beginning of
	 * the buffer that come from later in the buffer.
	 */

	while (p < oPtrSave)
		ChOut (*p++);

	/*
	 * Add trigger character.  If this is a newline, it will cause the
	 * final word to be flushed on its own line.  Otherwise it'll just
	 * add whitespace after the word on the (new) current line.
	 */

	ChOut (trigger);
}


/*
 * Table element routines
 */

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

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

	tblColIdx = 0;
	tblRowIdx = 0;

	Break ();
}


static void
TableEnd (void)
{
	Break ();
}


static void
TableColumnInfo (short argc, char **argv)
{
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".
	 */
	colSep[tblColIdx] = StrToChars (argv[colSepArg]);
	colWidth[tblColIdx] = StrToChars (argv[colWidthArg]);

	colEqWidth[tblColIdx] = (argv[colEqWidthArg][0] == 'y' ? 1 : 0);

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

	/*
	 * Have gotten information for all columns now.  Determine the
	 * right edges and total table width.  This is a very simplistic
	 * algorithm.
	 */

	tblWidth = 0;

	for (i = 0; i < tblCols; i++)
	{
		if (colWidth[i] == 0)
			colWidth[i] = defColWidth;
		if (colSep[i] == 0)
			colSep[i] = defColSep;
		tblWidth += colWidth[i] + colSep[i];
		colRtEdge[i] = tblWidth;
/*
ETMMsg ("col %hd, edge %hd", i, colRtEdge[i]);
*/
	}

	/* probably could expand widths here if table should be expanded */
}


/*
 * 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)
{
	Break ();
	++tblRowIdx;	/* done with row, bump counter */
}


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

	for (i = 0; i < colRtEdge[tblCols-1]; i++)
		ChOut ('-');
	Break ();
	++tblRowIdx;	/* done with row, bump counter */
}



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

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

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

	vspan = StrToShort (argv[cellVSpanArg]);
	if (vspan == 1)			/* not part of a vspan */
	{
		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]);
	/* vertical adjust is ignored */
	/* borders are ignored */

	/*
	 * 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.
	 */

	++tblColIdx;
	if (tblColIdx >= tblCols)
		tblColIdx = 0;
}


static void
TableCellBegin (short argc, char **argv)
{
	TabToTableCol ();
}


static void
TableCellEnd (void)
{
	++tblColIdx;
}


static void
TableEmptyCell (void)
{
	++tblColIdx;
}


static void
TableSpannedCell (void)
{
	++tblColIdx;
}


/*
 * Draw a line as wide as the current cell and any following cells that
 * it spans horizontally.
 */

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

	TabToTableCol ();
	prevRtEdge = (tblColIdx == 0 ? 0 : colRtEdge[tblColIdx-1]);
	i = colRtEdge[tblColIdx + cellHSpan[tblColIdx] - 1] - prevRtEdge;
	while (i-- > 0)
		ChOut ('-');
	++tblColIdx;
}


/*
 * Space over to the beginning of the current table column.  (Do nothing
 * for first column.)
 */

static void
TabToTableCol (void)
{
short	i;

	if (tblColIdx > 0)
	{
/*
ETMMsg ("currently at %ld, tab to %hd", oPos, colRtEdge[tblColIdx-1]);
*/
		i = colRtEdge[tblColIdx-1] - oPos;
		while (i-- > 0)
			ChOut (' ');
	}
}
